Преглед изворни кода

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

Tony Kang пре 3 месеци
родитељ
комит
54bfeb1141
62 измењених фајлова са 3197 додато и 800 уклоњено
  1. 30 1
      app/controller/contract_controller.js
  2. 150 22
      app/controller/cost_controller.js
  3. 540 15
      app/public/js/contract_detail.js
  4. 340 0
      app/public/js/cost_compare.js
  5. 186 0
      app/public/js/cost_gather.js
  6. 104 19
      app/public/js/cost_stage_analysis.js
  7. 55 30
      app/public/js/cost_stage_book.js
  8. 70 35
      app/public/js/cost_stage_ledger.js
  9. 6 3
      app/public/js/cost_tmpl.js
  10. 4 2
      app/public/js/gcl_gather.js
  11. 1 1
      app/public/js/ledger.js
  12. 2 2
      app/public/js/ledger_gather.js
  13. 1 1
      app/public/js/shares/common_audit.js
  14. 5 2
      app/public/js/shares/cs_tools.js
  15. 2 2
      app/public/js/spreadjs_rela/spreadjs_zh.js
  16. 10 0
      app/router.js
  17. 2 2
      app/service/calc_tmpl.js
  18. 159 0
      app/service/contract_supplement.js
  19. 61 0
      app/service/contract_supplement_att.js
  20. 10 5
      app/service/cost_stage.js
  21. 25 8
      app/service/cost_stage_analysis.js
  22. 5 5
      app/service/cost_stage_analysis_detail.js
  23. 6 1
      app/service/cost_stage_audit.js
  24. 12 1
      app/service/cost_stage_book.js
  25. 18 45
      app/service/cost_stage_book_detail.js
  26. 50 23
      app/service/cost_stage_detail.js
  27. 15 2
      app/service/cost_stage_ledger.js
  28. 8 2
      app/service/phase_pay.js
  29. 7 2
      app/service/safe_stage.js
  30. 23 0
      app/service/sub_project.js
  31. 26 4
      app/view/contract/detail.ejs
  32. 81 0
      app/view/contract/detail_modal.ejs
  33. 5 0
      app/view/cost/analysis.ejs
  34. 7 5
      app/view/cost/analysis_list.ejs
  35. 2 1
      app/view/cost/analysis_list_modal.ejs
  36. 2 1
      app/view/cost/analysis_menu_list.ejs
  37. 1 0
      app/view/cost/analysis_modal.ejs
  38. 3 1
      app/view/cost/audit_modal.ejs
  39. 23 11
      app/view/cost/book_list.ejs
  40. 1 1
      app/view/cost/book_list_modal.ejs
  41. 2 1
      app/view/cost/book_menu_list.ejs
  42. 205 0
      app/view/cost/compare.ejs
  43. 1 0
      app/view/cost/compare_modal.ejs
  44. 33 0
      app/view/cost/gather.ejs
  45. 46 0
      app/view/cost/gather_modal.ejs
  46. 2 0
      app/view/cost/ledger.ejs
  47. 47 11
      app/view/cost/ledger_list.ejs
  48. 45 0
      app/view/cost/ledger_list_modal.ejs
  49. 2 1
      app/view/cost/ledger_menu_list.ejs
  50. 47 0
      app/view/cost/ledger_modal.ejs
  51. 1 1
      app/view/cost/list_menu_list.ejs
  52. 3 3
      app/view/dashboard/index.ejs
  53. 6 1
      app/view/ledger/gather.ejs
  54. 2 2
      app/view/setting/limit.ejs
  55. 2 2
      app/view/setting/s2b.ejs
  56. 25 1
      app/view/sp_setting/fun.ejs
  57. 27 0
      app/view/sp_setting/manage_modal.ejs
  58. 1 1
      config/config.default.js
  59. 44 0
      config/web.js
  60. 5 520
      sql/update.sql
  61. 1 1
      sql/update20260403.sql
  62. 592 0
      sql/update20260527.sql

+ 30 - 1
app/controller/contract_controller.js

@@ -588,6 +588,7 @@ module.exports = app => {
                         responseData.data.contract = contract;
                         responseData.data.pays = await ctx.service.contractPay.getPays(options, data.postData);
                         responseData.data.files = await ctx.service.contractAtt.getAtt(data.postData);
+                        responseData.data.supplements = await ctx.service.contractSupplement.getSupplements(options, data.postData);
                         break;
                     case 'add-contract-pay':
                         responseData.data = await ctx.service.contractPay.add(options, data.postData.select, data.postData.pay);
@@ -598,6 +599,15 @@ module.exports = app => {
                     case 'del-contract-pay':
                         responseData.data = await ctx.service.contractPay.del(options, data.postData.select, data.postData.pay);
                         break;
+                    case 'add-contract-supplement':
+                        responseData.data = await ctx.service.contractSupplement.add(options, data.postData.select, data.postData.supplement);
+                        break;
+                    case 'save-contract-supplement':
+                        responseData.data = await ctx.service.contractSupplement.save(options, data.postData.select, data.postData.supplement);
+                        break;
+                    case 'del-contract-supplement':
+                        responseData.data = await ctx.service.contractSupplement.del(options, data.postData.select, data.postData.supplement);
+                        break;
                     default:
                         throw '未知操作';
                 }
@@ -689,6 +699,7 @@ module.exports = app => {
                 const cid = ctx.params.cid;
                 if (!cid) throw '参数有误';
                 const cpid = parseInt(ctx.params.cpid);
+                const csid = parseInt(ctx.params.csid);
                 const parts = this.ctx.multipart({
                     autoFields: true,
                 });
@@ -702,7 +713,7 @@ module.exports = app => {
                     }
                     const fileInfo = path.parse(stream.filename);
                     const headpath = ctx.contractOptions.spid ? `sp/contract/${ctx.contractOptions.spid}` : `app/public/upload/${ctx.contractOptions.tid}`;
-                    const filepath = `${headpath}/contract/${cid}${cpid ? '/pay/' + cpid : ''}/fujian_${create_time + idx.toString() + fileInfo.ext}`;
+                    const filepath = `${headpath}/contract/${cid}${cpid ? '/pay/' + cpid : (csid ? '/supplement/' + csid : '')}/fujian_${create_time + idx.toString() + fileInfo.ext}`;
                     // await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, 'app', filepath));
                     await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
                     files.push({ filepath, name: stream.filename, ext: fileInfo.ext });
@@ -732,6 +743,9 @@ module.exports = app => {
                     if (cpid) {
                         newFile.cpid = cpid;
                     }
+                    if (csid) {
+                        newFile.csid = csid;
+                    }
                     return newFile;
                 });
                 if (cpid) {
@@ -739,6 +753,11 @@ module.exports = app => {
                     await ctx.service.contractPayAtt.saveFileMsgToDb(payload);
                     // 将最新的当前标段的所有文件信息返回
                     responseData.data = await ctx.service.contractPayAtt.getAtt(cpid);
+                } else if (csid) {
+                    // 执行文件信息写入数据库
+                    await ctx.service.contractSupplementAtt.saveFileMsgToDb(payload);
+                    // 将最新的当前标段的所有文件信息返回
+                    responseData.data = await ctx.service.contractSupplementAtt.getAtt(csid);
                 } else {
                     // 执行文件信息写入数据库
                     await ctx.service.contractAtt.saveFileMsgToDb(payload);
@@ -760,12 +779,15 @@ module.exports = app => {
         async deleteFile(ctx) {
             try {
                 const cpid = ctx.params.cpid;
+                const csid = ctx.params.csid;
                 const cid = ctx.params.cid;
                 const responseData = { err: 0, msg: '', data: {} };
                 const data = JSON.parse(ctx.request.body.data);
                 let fileInfo = null;
                 if (cpid) {
                     fileInfo = await ctx.service.contractPayAtt.getDataById(data.id);
+                } else if (csid) {
+                    fileInfo = await ctx.service.contractSupplementAtt.getDataById(data.id);
                 } else if (cid) {
                     fileInfo = await ctx.service.contractAtt.getDataById(data.id);
                 } else {
@@ -778,6 +800,8 @@ module.exports = app => {
                     // 再删除数据库
                     if (cpid) {
                         await ctx.service.contractPayAtt.delete(data.id);
+                    } else if (csid) {
+                        await ctx.service.contractSupplementAtt.delete(data.id);
                     } else {
                         await ctx.service.contractAtt.delete(data.id);
                     }
@@ -786,6 +810,8 @@ module.exports = app => {
                 }
                 if (cpid) {
                     responseData.data = await ctx.service.contractPayAtt.getAtt(cpid);
+                } else if (csid) {
+                    responseData.data = await ctx.service.contractSupplementAtt.getAtt(csid);
                 } else {
                     responseData.data = await ctx.service.contractAtt.getAtt(cid);
                 }
@@ -806,10 +832,13 @@ module.exports = app => {
             if (id) {
                 try {
                     const cpid = ctx.params.cpid;
+                    const csid = ctx.params.csid;
                     const cid = ctx.params.cid;
                     let fileInfo = null;
                     if (cpid) {
                         fileInfo = await ctx.service.contractPayAtt.getDataById(id);
+                    } else if (csid) {
+                        fileInfo = await ctx.service.contractSupplementAtt.getDataById(id);
                     } else if (cid) {
                         fileInfo = await ctx.service.contractAtt.getDataById(id);
                     } else {

+ 150 - 22
app/controller/cost_controller.js

@@ -9,7 +9,6 @@
  */
 const audit = require('../const/audit');
 const shenpiConst = require('../const/shenpi');
-const moment = require('moment');
 const sendToWormhole = require('stream-wormhole');
 const fs = require('fs');
 const path = require('path');
@@ -88,8 +87,8 @@ module.exports = app => {
             }
         }
 
-        async getTypeStages(ctx, stage_type) {
-            const stages = await this.ctx.service.costStage.getAllStages(ctx.tender.id, stage_type, 'DESC');
+        async getTypeStages(ctx, stage_type, sort = 'DESC') {
+            const stages = await this.ctx.service.costStage.getAllStages(ctx.tender.id, stage_type, sort);
             for (const s of stages) {
                 if (s.audit_status !== audit.common.status.checked) {
                     await this.ctx.service.costStage.loadUser(s);
@@ -110,7 +109,9 @@ module.exports = app => {
             try {
                 const stage_type = this.ctx.service.costStage.stageType.ledger.key;
                 const stages = await this.getTypeStages(ctx, stage_type);
+                const colSet = this.ctx.service.subProject.getColSet('cost_' + stage_type);
                 const renderData = {
+                    colSet,
                     stage_type,
                     auditType: audit.auditType,
                     stages,
@@ -120,29 +121,33 @@ module.exports = app => {
                 await this.layout('cost/ledger_list.ejs', renderData, 'cost/ledger_list_modal.ejs');
             } catch(err) {
                 ctx.log(err);
-                ctx.redirect(ctx.request.header.referer);
+                ctx.redirect(`/sp/${ctx.subProject.id}/cost`);
             }
         }
         async book(ctx) {
             try {
                 const stage_type = this.ctx.service.costStage.stageType.book.key;
                 const stages = await this.getTypeStages(ctx, stage_type);
+                for (const s of stages) {
+                    s.relaStage = await this.ctx.service.costStage.getStage(s.rela_stage.sid);
+                }
                 const validLedgerStages = await this.ctx.service.costStage.getAllCheckedStages(ctx.tender.id, 'ledger', 'DESC');
                 for (const vls of validLedgerStages) {
                     vls.beenRela = stages.findIndex(x => { return x.rela_stage.sid === vls.id; }) >= 0;
+                    vls.canRela = vls.stage_order === stages.length + 1;
                 }
                 const renderData = {
                     stage_type,
                     auditType: audit.auditType,
                     stages,
-                    validLedgerStages: validLedgerStages.filter(x => { return !x.beenRela; }),
+                    validLedgerStages: validLedgerStages.filter(x => { return x.canRela; }), //validLedgerStages.filter(x => { return !x.beenRela; }),
                     auditConst: audit.common,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.cost.cost_stage)
                 };
                 await this.layout('cost/book_list.ejs', renderData, 'cost/book_list_modal.ejs');
             } catch(err) {
                 ctx.log(err);
-                ctx.redirect(ctx.request.header.referer);
+                ctx.redirect(`/sp/${ctx.subProject.id}/cost`);
             }
         }
         async analysis(ctx) {
@@ -150,13 +155,15 @@ module.exports = app => {
                 const stage_type = this.ctx.service.costStage.stageType.analysis.key;
                 const stages = await this.getTypeStages(ctx, stage_type);
                 // todo 兼容以ledger做关联的情况,届时只需在标段或者项目上设置这个类型即可。
-                const analysisType = 'book';
+                const analysisType = this.ctx.subProject.fun_rela.costAnalysisType;
                 let validRelaStages = await this.ctx.service.costStage.getAllCheckedStages(ctx.tender.id, analysisType, 'DESC');
                 if (stages.length > 0) {
                     const checkedStage = stages.find(x => { return x.audit_status === audit.common.status.checked; });
                     if (checkedStage) validRelaStages = validRelaStages.filter( x => { return x.stage_order > checkedStage.rela_stage.sorder; });
                 }
                 const renderData = {
+                    analysisType,
+                    analysisTypeStr: this.ctx.service.costStage.stageType[analysisType].name,
                     stage_type,
                     auditType: audit.auditType,
                     stages,
@@ -167,7 +174,7 @@ module.exports = app => {
                 await this.layout('cost/analysis_list.ejs', renderData, 'cost/analysis_list_modal.ejs');
             } catch(err) {
                 ctx.log(err);
-                ctx.redirect(ctx.request.header.referer);
+                ctx.redirect(`/sp/${ctx.subProject.id}/cost`);
             }
         }
 
@@ -192,10 +199,10 @@ module.exports = app => {
                     if (!ledgerStage) throw '选择的关联成本不存在';
                     newStage = await ctx.service.costStage.add(ctx.tender.id, stage_type, ledgerStage.stage_date, { sid: ledgerStage.id, sorder: ledgerStage.stage_order });
                 } else if (stage_type === 'analysis') {
+                    const analysisType = this.ctx.subProject.fun_rela.costAnalysisType;
+                    if (analysisType !== ctx.request.body.analysis_type) throw '成本分析应关联的成本类型发生改变,请在界面刷新后再试';
                     const stage_order = ctx.request.body.stage;
                     if (!stage_order) throw '请选择关联期';
-                    // todo 兼容以ledger做关联的情况,届时只需在标段或者项目上设置这个类型即可。
-                    const analysisType = 'book';
                     const relaStage = await ctx.service.costStage.getStageByOrder(ctx.tender.id, analysisType, stage_order);
                     if (!relaStage) throw '选择的关联期不存在';
                     newStage = await ctx.service.costStage.add(ctx.tender.id, stage_type, relaStage.stage_date, { sid: relaStage.id, sorder: relaStage.stage_order });
@@ -276,6 +283,7 @@ module.exports = app => {
                     });
                     payStages = stages.map(x => { return { order: x.order, date: x.s_time }; });
                 }
+                const colSet = this.ctx.service.subProject.getColSet('cost_' + ctx.costStage.stage_type);
                 const renderData = {
                     auditConst: audit.common,
                     auditType: audit.auditType,
@@ -288,6 +296,7 @@ module.exports = app => {
                     shenpi_status: ctx.tender.info.shenpi[stageTypeInfo.shenpi_status] || 1,
                     contractTypes,
                     contractUsed,
+                    colSet,
                 };
                 await this.layout(`cost/${stageTypeInfo.key}.ejs`, renderData, `cost/${stageTypeInfo.key}_modal.ejs`);
             } catch (err) {
@@ -305,7 +314,7 @@ module.exports = app => {
                 for (const f of filter) {
                     switch (f) {
                         case 'bills':
-                            responseData.data.bills = ctx.costStage.readOnly
+                            responseData.data.bills = ctx.costStage.readOnly && !ctx.costStage.canCheck
                                 ? await ctx.service.costStageLedger.getReadData(ctx.costStage)
                                 : await ctx.service.costStageLedger.getEditData(ctx.costStage);
                             break;
@@ -313,12 +322,11 @@ module.exports = app => {
                             responseData.data[f] = await ctx.service.costStageLedger.getCompareData(ctx.costStage);
                             break;
                         case 'detail':
-                            responseData.data.detail =  ctx.costStage.readOnly
+                            responseData.data.detail =  ctx.costStage.readOnly && !ctx.costStage.canCheck
                                 ? await ctx.service.costStageDetail.getReadData(ctx.costStage)
                                 : await ctx.service.costStageDetail.getEditData(ctx.costStage);
                             break;
                         case 'detailCompare':
-                            // todo
                             responseData.data.detailCompare = await ctx.service.costStageDetail.getCompareData(ctx.costStage);
                             break;
                         case 'auditFlow':
@@ -363,7 +371,7 @@ module.exports = app => {
                         case 'bills':
                             const ledger = await ctx.service.costStageLedger.getReadData(ctx.costStage.relaStage);
                             ledger.forEach(l => { delete l.postil; delete l.memo; });
-                            const book = ctx.costStage.readOnly
+                            const book = ctx.costStage.readOnly && !ctx.costStage.canCheck
                                 ? await ctx.service.costStageBook.getReadData(ctx.costStage)
                                 : await ctx.service.costStageBook.getEditData(ctx.costStage);
                             ctx.helper.assignRelaData(ledger, [
@@ -384,7 +392,7 @@ module.exports = app => {
                         case 'detail':
                             const detail = await ctx.service.costStageDetail.getReadData(ctx.costStage.relaStage);
                             detail.forEach(l => { delete l.postil; delete l.memo; });
-                            const bookDetail = ctx.costStage.readOnly
+                            const bookDetail = ctx.costStage.readOnly && !ctx.costStage.canCheck
                                 ? await ctx.service.costStageBookDetail.getReadData(ctx.costStage)
                                 : await ctx.service.costStageBookDetail.getEditData(ctx.costStage);
                             ctx.helper.assignRelaData(detail, [
@@ -428,7 +436,7 @@ module.exports = app => {
                 for (const f of filter) {
                     switch (f) {
                         case 'bills':
-                            responseData.data.bills = ctx.costStage.readOnly
+                            responseData.data.bills = ctx.costStage.readOnly && !ctx.costStage.canCheck
                                 ? await ctx.service.costStageAnalysis.getReadData(ctx.costStage)
                                 : await ctx.service.costStageAnalysis.getEditData(ctx.costStage);
                             break;
@@ -436,17 +444,19 @@ module.exports = app => {
                             responseData.data[f] = await ctx.service.costStageAnalysis.getCompareData(ctx.costStage);
                             break;
                         case 'detail':
-                            responseData.data.detail =  ctx.costStage.readOnly
+                            responseData.data.detail =  ctx.costStage.readOnly && !ctx.costStage.canCheck
                                 ? await ctx.service.costStageAnalysisDetail.getReadData(ctx.costStage)
                                 : await ctx.service.costStageAnalysisDetail.getEditData(ctx.costStage);
                             break;
                         case 'detailCompare':
-                            // todo
                             responseData.data.detailCompare = await ctx.service.costStageAnalysisDetail.getCompareData(ctx.costStage);
                             break;
                         case 'auditFlow':
                             responseData.data[f] = await ctx.service.costStageAudit.getViewFlow(ctx.costStage);
                             break;
+                        case 'att':
+                            responseData.data[f] = await ctx.service.costStageFile.getData(ctx.costStage.id, 'DESC');
+                            break;
                         default:
                             responseData.data[f] = [];
                             break;
@@ -706,9 +716,9 @@ module.exports = app => {
 
                 const auditorInfo = await this.ctx.service.projectAccount.getDataById(id);
                 if (!auditorInfo) throw '添加的审批人不存在';
-
-                const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.safe_payment, sp_status: shenpiConst.sp_status.gdzs });
-                const is_gdzs = shenpiInfo && ctx.tender.info.shenpi.safe_payment === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const typeInfo = ctx.service.costStage.stageType[ctx.costStage.stage_type];
+                const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.costStage.tid, sp_type: shenpiConst.cost_sp_type[typeInfo.shenpi_status], sp_status: shenpiConst.sp_status.gdzs });
+                const is_gdzs = shenpiInfo && ctx.tender.info.shenpi[typeInfo.shenpi_status] === shenpiConst.sp_status.gdzs ? 1 : 0;
                 const result = await ctx.service.costStageAudit.addAuditor(ctx.costStage.id, auditorInfo, ctx.costStage.audit_times, is_gdzs);
                 if (!result) throw '添加审核人失败';
 
@@ -786,6 +796,7 @@ module.exports = app => {
                     const code = ctx.request.body.code;
                     const cacheKey = 'smsCode:' + ctx.session.sessionUser.accountId;
                     const cacheCode = await app.redis.get(cacheKey);
+                    const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
                     if (cacheCode === null || code === undefined || cacheCode !== (code + pa.auth_mobile)) {
                         throw '验证码不正确!';
                     }
@@ -831,7 +842,124 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: error.toString(), data: null };
             }
         }
+
+        async colSet(ctx) {
+            const stage_type = ctx.request.body.stage_type;
+            try {
+                const colSet = {};
+                for (const prop in ctx.request.body) {
+                    if (prop.indexOf('col_') === 0) colSet[prop.replace('col_', '')] = ctx.request.body[prop] === '1' ? 1 : 0;
+                }
+                await this.ctx.service.subProject.updateColSet('cost_' + stage_type, colSet);
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '保存列设置失败');
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        async compare(ctx) {
+            const stageTypeInfo = ctx.service.costStage.stageType[ctx.costStage.stage_type];
+            try {
+                await this._getStageAuditViewData(ctx);
+                // 流程审批人相关数据
+                const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
+                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
+                const accountGroup = unitList.map(item => {
+                    const groupList = accountList.filter(item1 => item1.company === item.name);
+                    return { groupName: item.name, groupList };
+                }).filter(x => { return x.groupList.length > 0; });
+                // 是否已验证手机短信
+                const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const calcFields = ctx.service[stageTypeInfo.dataService].calcFields;
+
+                const renderData = {
+                    auditConst: audit.common,
+                    auditType: audit.auditType,
+                    accountList,
+                    accountGroup,
+                    shenpiConst,
+                    authMobile: pa.auth_mobile,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.cost.cost_compare),
+                    shenpi_status: ctx.tender.info.shenpi[stageTypeInfo.shenpi_status] || 1,
+                    calcFields,
+                };
+                await this.layout(`cost/compare.ejs`, renderData, `cost/compare_modal.ejs`);
+            } catch(err) {
+                ctx.log(err);
+                ctx.postError(err, '读取成本报审错误');
+                ctx.redirect(`/sp/${ctx.subProject.id}/cost/tender/${ctx.tender.id}/${stageTypeInfo.key}`);
+            }
+        }
+
+        async gather(ctx) {
+            try {
+                const ledgerStages = await this.getTypeStages(ctx, 'ledger', 'ASC');
+                const renderData = {
+                    ledgerStages,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.cost.cost_gather)
+                };
+                await this.layout('cost/gather.ejs', renderData, 'cost/gather_modal.ejs');
+            } catch(err) {
+                ctx.log(err);
+                ctx.postError(err, '获取汇总分析页面错误');
+                ctx.redirect(`/sp/${ctx.subProject.id}/cost`);
+            }
+        }
+        async _ledgerGatherLoad(stage) {
+            const result = {};
+            result.bills = stage.audit_status !== audit.common.status.checked
+                ? await this.ctx.service.costStageLedger.getReadData(stage)
+                : await this.ctx.service.costStageLedger.getEditData(stage);
+            return result;
+        }
+        async _bookGatherLoad(stage) {
+            const result = {};
+            stage.relaStage = await this.ctx.service.costStage.getStage(stage.rela_stage.sid);
+            result.bills = await this.ctx.service.costStageLedger.getReadData(stage.relaStage);
+            result.bills.forEach(l => { delete l.postil; delete l.memo; });
+            const book = stage.audit_status !== audit.common.status.checked
+                ? await this.ctx.service.costStageBook.getReadData(stage)
+                : await this.ctx.service.costStageBook.getEditData(stage);
+            this.ctx.helper.assignRelaData(result.bills, [
+                { data: book, fields: ['in_tp', 'in_excl_tax_tp', 'postil', 'memo'], prefix: '', relaId: 'ledger_id' },
+                { data: book, fields: ['id'], prefix: 'book_', relaId: 'ledger_id' },
+            ]);
+            return result;
+        }
+        async _analysisGatherLoad(stage) {
+            const result = {};
+            result.bills = stage.audit_status !== audit.common.status.checked
+                ? await this.ctx.service.costStageAnalysis.getReadData(stage)
+                : await this.ctx.service.costStageAnalysis.getEditData(stage);
+            return result;
+        }
+        async loadGatherData(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = {};
+
+                if (data.stages) {
+                    result.stages = [];
+                    for (const s of data.stages) {
+                        const stage = await this.ctx.service.costStage.getStage(s);
+                        const loadFun = `_${stage.stage_type}GatherLoad`;
+                        if (this[loadFun]) {
+                            const detail = await this[loadFun](stage);
+                            result.stages.push({ id: stage.id, stage_date: stage.stage_date, stage_order: stage.stage_order, detail });
+                        } else {
+                            throw '未知期数据类型';
+                        }
+                    }
+                }
+                ctx.body = { err: 0, msg: '', data: result }
+            } catch(err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取汇总数据错误');
+            }
+        }
     }
 
     return CostController;
-};
+};

+ 540 - 15
app/public/js/contract_detail.js

@@ -339,6 +339,7 @@ $(document).ready(function() {
                 $('#save_contract_btn').hide();
                 $('#cancel_contract_btn').hide();
                 $('#add_contract_pay_btn').hide();
+                $('#add_contract_supplement_btn').hide();
                 $('a[href*="#cons-upfile"]').hide();
                 if (need) {
                     for (const c of contractDetail) {
@@ -363,6 +364,7 @@ $(document).ready(function() {
                 $('#save_contract_btn').hide();
                 $('#cancel_contract_btn').hide();
                 $('#add_contract_pay_btn').hide();
+                $('#add_contract_supplement_btn').hide();
                 if (node.settle_code) {
                     $('#add_contract_pay_btn').hide();
                     if (need) {
@@ -390,26 +392,64 @@ $(document).ready(function() {
                 $('#save_contract_btn').hide();
                 $('#cancel_contract_btn').hide();
                 $('#add_contract_pay_btn').hide();
+                $('#add_contract_supplement_btn').hide();
                 $('a[href*="#cons-upfile"]').show();
+            } else if ($('.bc-bar .nav li .active').attr('href') === '#htsupplement') {
+                $('#edit_contract_btn').hide();
+                $('#save_contract_btn').hide();
+                $('#cancel_contract_btn').hide();
+                $('#add_contract_pay_btn').hide();
+                $('#add_contract_supplement_btn').hide();
+                if (node.settle_code) {
+                    $('#add_contract_supplement_btn').hide();
+                    if (need) {
+                        $('#htsupplement-table tbody').find('.supplement-edit').hide();
+                        $('#htsupplement-table tbody').find('.supplement-del').hide();
+                    }
+                } else {
+                    if ((node.uid === user_id || permission_edit_contract) && (!shenpi_status || (shenpi_status && node.status === auditConst.status.checked))) $('#add_contract_supplement_btn').show();
+                    if (need && contractSupplements && contractSupplements.length > 0) {
+                        for (const [i, cp] of contractSupplements.entries()) {
+                            if (cp.uid === user_id && (!shenpi_status || (shenpi_status && (cp.status === auditConst.status.uncheck || cp.status === auditConst.status.checkNo)))) {
+                                $('#htsupplement-table tbody tr').eq(i).find('.supplement-edit').show();
+                                $('#htsupplement-table tbody tr').eq(i).find('.supplement-del').show();
+                            } else if (shenpi_status && cp.status === auditConst.status.checked && is_admin) {
+                                $('#htsupplement-table tbody tr').eq(i).find('.supplement-del').show();
+                            } else if (shenpi_status && (cp.status === auditConst.status.checking || cp.status === auditConst.status.checkNoPre) && cp.curAuditorIds && cp.curAuditorIds.length > 0 && cp.curAuditorIds.indexOf(user_id) !== -1) {
+                                $('#htsupplement-table tbody tr').eq(i).find('.supplement-edit').show();
+                            }
+                        }
+                    }
+                }
+                $('a[href*="#cons-upfile"]').hide();
             }
         },
         openContractPayFiles: function (pay) {
             this.setContractFiles(pay.files, pay.cid, pay.id, '#cons-pay-file table tbody');
         },
+        openContractSupplementFiles: function (supplement) {
+            this.setContractFiles(supplement.files, supplement.cid, supplement.id, '#cons-supplement-file table tbody');
+        },
         checkCloseStatus: function (node) {
+            let have_uncheck = false;
             if (pay_shenpi_status) {
-                let have_uncheck = false;
                 for (const pay of contractPays) {
                     if (pay.status !== auditConst.status.checked) {
                         have_uncheck = true;
                         break;
                     }
                 }
-                if (have_uncheck) {
-                    $('a[href*="#cons-close"]').hide();
-                } else if ((node.uid === user_id || permission_edit_contract) && !node.settle_code) {
-                    $('a[href*="#cons-close"]').show();
+            }
+            if (!have_uncheck && shenpi_status) {
+                for (const supplement of contractSupplements) {
+                    if (supplement.status !== auditConst.status.checked) {
+                        have_uncheck = true;
+                        break;
+                    }
                 }
+            }
+            if (have_uncheck) {
+                $('a[href*="#cons-close"]').hide();
             } else if ((node.uid === user_id || permission_edit_contract) && !node.settle_code) {
                 $('a[href*="#cons-close"]').show();
             }
@@ -455,7 +495,7 @@ $(document).ready(function() {
                 }
                 $('#shenpi_btn').html(shenpi_html);
                 if (node.uid === user_id || permission_edit_contract) {
-                    contractTreeSpreadObj.changeContractTab(node);
+                    contractTreeSpreadObj.changeContractTab(node, true);
                 } else {
                     $('#edit_contract_btn').hide();
                     $('#save_contract_btn').hide();
@@ -467,6 +507,11 @@ $(document).ready(function() {
                     } else {
                         $('#add_contract_pay_btn').hide();
                     }
+                    if (node && node.c_code && $('.bc-bar .nav li .active').attr('href') === '#htsupplement' && (node.uid === user_id || permission_edit_contract) && node.status === auditConst.status.checked) {
+                        $('#add_contract_supplement_btn').show();
+                    } else {
+                        $('#add_contract_supplement_btn').hide();
+                    }
                 }
             } else if ((node.uid === user_id || permission_edit_contract)) {
                 if (node.settle_code) {
@@ -476,7 +521,7 @@ $(document).ready(function() {
                     $('a[href*="#cons-unlock"]').hide();
                     contractTreeSpreadObj.checkCloseStatus(node);
                 }
-                contractTreeSpreadObj.changeContractTab(node);
+                contractTreeSpreadObj.changeContractTab(node, true);
             } else {
                 $('#edit_contract_btn').hide();
                 $('#save_contract_btn').hide();
@@ -490,6 +535,11 @@ $(document).ready(function() {
                 } else {
                     $('#add_contract_pay_btn').hide();
                 }
+                if (node && node.c_code && $('.bc-bar .nav li .active').attr('href') === '#htsupplement' && (node.uid === user_id || permission_edit_contract)) {
+                    $('#add_contract_supplement_btn').show();
+                } else {
+                    $('#add_contract_supplement_btn').hide();
+                }
             }
         },
         setContractFiles: function (files, cid, cpid = null, _this = '#htfile-table tbody') {
@@ -515,6 +565,9 @@ $(document).ready(function() {
             });
             $(_this).html(filesHtml);
         },
+        setContractListHtml: function (type, node, list) {
+            reOrderTenders(type, node, list);
+        },
         setContractPays: function (pays, node) {
             contractPays = pays;
             let paysHtml = '';
@@ -554,6 +607,45 @@ $(document).ready(function() {
             });
             $('#htpay-table tbody').html(paysHtml);
         },
+        setContractSupplements: function (supplements, node) {
+            contractSupplements = supplements;
+            let supplementsHtml = '';
+            const newSupplements = supplements.map(supplement => {
+                let showEdit = false;
+                let showDel = false;
+                if (supplement.uid === user_id && !node.settle_code && (!shenpi_status || (shenpi_status && (supplement.status === auditConst.status.uncheck || supplement.status === auditConst.status.checkNo)))) {
+                    showEdit = true;
+                    showDel = true;
+                } else if (!node.settle_code && shenpi_status && supplement.status === auditConst.status.checked && is_admin) {
+                    showDel = true;
+                } else if (!node.settle_code && shenpi_status && (supplement.status === auditConst.status.checking || supplement.status === auditConst.status.checkNoPre) && supplement.curAuditorIds && supplement.curAuditorIds.length > 0 && supplement.curAuditorIds.indexOf(user_id) !== -1) {
+                    showEdit = true;
+                }
+                return {...supplement, showEdit, showDel}
+            })
+            console.log(supplements);
+            newSupplements.forEach((supplement, idx) => {
+                const shenpi_html = setSupplementShenpiHtml(supplement, false, node);
+                const operationHtml = `<a href="javascript:void(0);" class="text-primary supplement-edit" data-id="${supplement.id}" ${!supplement.showEdit ? `style="display:none"` : ''}>编辑</a> <a href="javascript:void(0);" class="text-danger supplement-del" data-id="${supplement.id}" ${!supplement.showDel ? `style="display:none"` : ''}>删除</a>`;
+                supplementsHtml += `<tr class="text-center" data-csid="${supplement.id}">
+                                        <td>${idx + 1}</td>
+                                        <td>${supplement.code}</td>
+                                        <td>${supplement.name}</td>
+                                        <td>${supplement.price}</td>
+                                        <td>${supplement.party_a}</td>
+                                        <td>${supplement.party_b}</td>
+                                        <td>${supplement.sign_time ? moment(supplement.sign_time).format('YYYY-MM-DD') : ''}</td>
+                                        <td>${supplement.address}</td>
+                                        <td>${supplement.username}</td>
+                                        <td>${moment(supplement.create_time).format('YYYY-MM-DD HH:mm:ss')}</td>
+                                        <td>${supplement.content}</td>
+                                        <td><a href="javascript:void(0);" class="text-primary open-supplement-files" data-csid="${supplement.id}"><i class="fa fa-paperclip fa-rotate-90"></i></a> <span class="files-num">${supplement.files.length > 0 ? supplement.files.length : ''}</span></td>
+                                        ${shenpi_html}
+                                        <td>${operationHtml}</td>
+                                    </tr>`;
+            });
+            $('#htsupplement-table tbody').html(supplementsHtml);
+        },
         setContract: function (sheet, needPost = true) {
             const node = SpreadJsObj.getSelectObject(sheet);
 
@@ -562,6 +654,7 @@ $(document).ready(function() {
                 $('#htdetail-attr').show();
                 $('#htpay-table').show();
                 $('#htfile-table').show();
+                $('#htsupplement-table').show();
                 for (const c of contractDetail) {
                     if (c === 'create_time') {
                         $('#htdetail_' + c).text(node[c] ? moment(node[c]).format('YYYY-MM-DD HH:mm:ss') : '');
@@ -581,8 +674,11 @@ $(document).ready(function() {
                     postData(window.location.pathname + '/update', {postType: 'get-contract', postData: node.id}, function (result) {
                         const refreshNode = contractTree.loadPostData({ update: result.contract });
                         contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
-                        contractTreeSpreadObj.setContractPays(result.pays, node);
+                        // contractTreeSpreadObj.setContractPays(result.pays, node);
+                        contractTreeSpreadObj.setContractListHtml('pay', node, result.pays);
                         contractTreeSpreadObj.setContractFiles(result.files, node.id);
+                        // contractTreeSpreadObj.setContractSupplements(result.supplements, node.id);
+                        contractTreeSpreadObj.setContractListHtml('supplement', node, result.supplements);
                         contractTreeSpreadObj.setContractBtn(result.contract);
                     });
                 }
@@ -591,6 +687,7 @@ $(document).ready(function() {
                 $('#htdetail-attr').hide();
                 $('#htpay-table').hide();
                 $('#htfile-table').hide();
+                $('#htsupplement-table').hide();
                 $('#edit_contract_btn').hide();
                 $('#save_contract_btn').hide();
                 $('#cancel_contract_btn').hide();
@@ -599,6 +696,7 @@ $(document).ready(function() {
                 $('a[href*="#cons-upfile"]').hide();
                 if (node && node.c_code && $('.bc-bar .nav li .active').attr('href') === '#htfile' && permission_att) $('a[href*="#cons-upfile"]').show();
                 $('#add_contract_pay_btn').hide();
+                $('#add_contract_supplement_btn').hide();
                 $('#shenpi_btn').html('');
             }
         },
@@ -961,7 +1059,7 @@ $(document).ready(function() {
             if (select.c_code) {
                 if (!(select.uid === user_id || is_admin)) {
                     canDelete = false;
-                } else if (select.exist_pay) {
+                } else if (select.exist_pay || select.exist_supplement) {
                     canDelete = false;
                 }
             } else {
@@ -974,7 +1072,7 @@ $(document).ready(function() {
                         if (!(sc.uid === user_id || is_admin)) {
                             canDelete = false;
                             break;
-                        } else if (sc.exist_pay) {
+                        } else if (sc.exist_pay || sc.exist_supplement) {
                             canDelete = false;
                             break;
                         }
@@ -986,6 +1084,93 @@ $(document).ready(function() {
     };
     contractSpread.bind(spreadNS.Events.SelectionChanged, contractTreeSpreadObj.selectionChanged);
     contractSpread.bind(spreadNS.Events.topRowChanged, contractTreeSpreadObj.topRowChanged);
+
+    // 支付/回款,补充合同列表排序及切换排序
+    let payOrderSetting = getLocalCache('jl-contract-pay-list-order-' + spid);
+    if (!payOrderSetting) payOrderSetting = 'create_time|down';
+
+    let supplementOrderSetting = getLocalCache('jl-contract-supplement-list-order-' + spid);
+    if (!supplementOrderSetting) supplementOrderSetting = 'create_time|down';
+    const pinyin = new PinYinOrder();
+    function CompareStr (x, y) {
+        return pinyin.compareWord(x, y);
+    }
+
+    function reOrderTenders (type, node = null, ts = null, orderStr = '') {
+        let orders = [];
+        if (type === 'pay') {
+            if (orderStr) {
+                payOrderSetting = orderStr;
+                setLocalCache('jl-contract-pay-list-order-' + spid, orderStr);
+            }
+            if (!ts) ts = contractPays;
+            orders = payOrderSetting.split('|');
+        } else if (type === 'supplement') {
+            if (orderStr) {
+                supplementOrderSetting = orderStr;
+                setLocalCache('jl-contract-supplement-list-order-' + spid, orderStr);
+            }
+            if (!ts) ts = contractSupplements;
+            orders = supplementOrderSetting.split('|');
+        }
+        if (orders[0] === 'code') {
+            ts.sort(function (a, b) {
+                // return orders[1] === 'up'
+                //     ? a[orders[0]].localeCompare(b[orders[0]], 'zh')
+                //     : -a[orders[0]].localeCompare(b[orders[0]], 'zh');
+                return orders[1] === 'up'
+                    ? CompareStr(a[orders[0]], b[orders[0]])
+                    : CompareStr(b[orders[0]], a[orders[0]]);
+            });
+        } else if (orders[0] === 'pay_time') {
+            ts.sort(function (a, b){
+                return orders[1] === 'up'
+                    ? Date.parse(a[orders[0]]) - Date.parse(b[orders[0]])
+                    : Date.parse(b[orders[0]]) - Date.parse(a[orders[0]]);
+            });
+        } else if (orders[0] === 'create_time') {
+            ts.sort(function (a, b){
+                return orders[1] === 'up'
+                    ? Date.parse(a[orders[0]]) - Date.parse(b[orders[0]])
+                    : Date.parse(b[orders[0]]) - Date.parse(a[orders[0]]);
+            })
+        }
+        if (!node) node = SpreadJsObj.getSelectObject(contractSheet);
+        setHeaderOrderButtonHtml(type);
+        if (type === 'pay') {
+            contractTreeSpreadObj.setContractPays(ts, node);
+        } else if (type === 'supplement') {
+            contractTreeSpreadObj.setContractSupplements(ts, node);
+        }
+    }
+    function setHeaderOrderButtonHtml(type) {
+        if (type === 'pay') {
+            $('#pay-header th[sort="pay_time"]').html((contract_type === contractConst.type.expenses ? '支付' : '回款') + '日期' + getOrderButton('pay_time', 'pay') + '</th>');
+            $('#pay-header th[sort="create_time"]').html('创建时间' + getOrderButton('create_time', 'pay') + '</th>');
+        } else if (type === 'supplement') {
+            $('#supplement-header th[sort="code"]').html('编号' + getOrderButton('code', 'supplement') + '</th>');
+            $('#supplement-header th[sort="create_time"]').html('创建时间' + getOrderButton('create_time', 'supplement') + '</th>');
+        }
+    }
+    function getOrderButton(field, type) {
+        let orders = [];
+        if (type === 'pay') {
+            orders = payOrderSetting.split('|');
+        } else {
+            orders = supplementOrderSetting.split('|');
+        }
+        const button = field === orders[0]
+            ? (orders[1] === 'up'
+                ? `<i class="fa fa-sort-amount-asc" aria-hidden="true" data-filed="${field + '|down'}" data-type="${type}"></i>`
+                : `<i class="fa fa-sort-amount-desc" aria-hidden="true" data-filed="${field + '|up'}" data-type="${type}"></i>`)
+            : `<i class="fa fa-sort" aria-hidden="true" data-filed="${field + '|up'}" data-type="${type}"></i>`;
+        return '<a href="javascript:void(0)" class="btn btn-sm ml-1" style="padding: 1px;">' + button + '</a>';
+    }
+    $('body').on('click', '#pay-header th[sort] a, #supplement-header th[sort] a', function (e) {
+        const field = $(this).find('i').data('filed');
+        const type = $(this).find('i').data('type');
+        reOrderTenders(type, null, null, field);
+    });
     // 右键菜单
     let batchInsertObj;
     $.contextMenu.types.batchInsert = function (item, opt, root) {
@@ -1637,9 +1822,11 @@ $(document).ready(function() {
         const node = SpreadJsObj.getSelectObject(contractSheet);
         $('a[href*="#cons-upfile"]').hide();
         $('#add_contract_pay_btn').hide();
+        $('#add_contract_supplement_btn').hide();
         if (node && node.c_code) {
             if ($('.bc-bar .nav li .active').attr('href') === '#htfile' && permission_att) $('a[href*="#cons-upfile"]').show();
             if ($('.bc-bar .nav li .active').attr('href') === '#htpay' && !node.settle_code && permission_add_pay && (!shenpi_status || (shenpi_status && node.status === auditConst.status.checked))) $('#add_contract_pay_btn').show();
+            if ($('.bc-bar .nav li .active').attr('href') === '#htsupplement' && !node.settle_code && permission_edit_contract && (!shenpi_status || (shenpi_status && node.status === auditConst.status.checked))) $('#add_contract_supplement_btn').show();
             if ((node.uid === user_id || permission_edit_contract)) contractTreeSpreadObj.changeContractTab(node, true);
         }
     });
@@ -1845,7 +2032,8 @@ $(document).ready(function() {
             postData(window.location.pathname + '/update', {postType: 'del-contract-pay', postData: { select: node.id, pay: cpid }}, function (result) {
                 const refreshNode = contractTree.loadPostData(result.node);
                 contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
-                contractTreeSpreadObj.setContractPays(result.pays, node);
+                // contractTreeSpreadObj.setContractPays(result.pays, node);
+                contractTreeSpreadObj.setContractListHtml('pay', node, result.pays);
                 contractTreeSpreadObj.checkCloseStatus(node);
                 contractTreeSpreadObj.setContract(contractSheet, false);
             })
@@ -1917,7 +2105,6 @@ $(document).ready(function() {
             $('#cons-addpay select[name="used"]').attr('disabled', true);
             $('#cons-addpay input[name="pay_price"]').attr('readonly', true);
             $('#cons-addpay input[name="debit_price"]').attr('readonly', true);
-            $('#cons-addpay input[name="yf_price"]').attr('readonly', true);
             $('#cons-addpay select[name="pay_type"]').attr('disabled', true);
             $('#cons-addpay textarea[name="remark"]').attr('readonly', true);
         } else {
@@ -1925,7 +2112,6 @@ $(document).ready(function() {
             $('#cons-addpay select[name="used"]').removeAttr('readonly');
             $('#cons-addpay input[name="pay_price"]').removeAttr('readonly');
             $('#cons-addpay input[name="debit_price"]').removeAttr('readonly');
-            $('#cons-addpay input[name="yf_price"]').removeAttr('readonly');
             $('#cons-addpay select[name="pay_type"]').removeAttr('readonly');
             $('#cons-addpay textarea[name="remark"]').removeAttr('readonly');
         }
@@ -1991,6 +2177,162 @@ $(document).ready(function() {
         }, '确认删除该文件?');
     });
 
+    $('body').on('click', '#htsupplement .supplement-del', function () {
+        const node = SpreadJsObj.getSelectObject(contractSheet);
+        const csid = $(this).data('id');
+        if (!node || !node.c_code) {
+            toastr.warning('请选择合同节点');
+            return;
+        }
+        if (node.settle_code) {
+            toastr.warning('已结算的合同不能删除补充合同');
+            return;
+        }
+        deleteAfterHint(function () {
+            postData(window.location.pathname + '/update', {postType: 'del-contract-supplement', postData: { select: node.id, supplement: csid }}, function (result) {
+                const refreshNode = contractTree.loadPostData(result.node);
+                contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
+                // contractTreeSpreadObj.setContractSupplements(result.supplements, node);
+                contractTreeSpreadObj.setContractListHtml('supplement', node, result.supplements);
+                contractTreeSpreadObj.checkCloseStatus(node);
+                contractTreeSpreadObj.setContract(contractSheet, false);
+            })
+        }, '确认删除该补充合同?');
+    });
+
+    const signTime = $('#cons-addsupplement input[name="sign_time"]').datepicker().data('datepicker');
+
+    $('#add_contract_supplement_btn').on('click', function () {
+        const node = SpreadJsObj.getSelectObject(contractSheet);
+        if (!node || !node.c_code) {
+            toastr.error('请选择一个合同节点');
+            return;
+        }
+        $('#cons-addsupplement .modal-title').text('新增补充合同');
+        $('#cons-addsupplement input[name="csid"]').val('');
+        $('#add-contract-supplement').show();
+        $('#save-contract-supplement').hide();
+        setSupplementModalInfo();
+        $('#cons-addsupplement').modal('show');
+    });
+
+    $('body').on('click', '#htsupplement .supplement-edit', function () {
+        const node = SpreadJsObj.getSelectObject(contractSheet);
+        if (!node || !node.c_code) {
+            toastr.error('请选择一个合同节点');
+            return;
+        }
+        const csid = $(this).data('id');
+        const csInfo = _.find(contractSupplements, { id: parseInt(csid) });
+        if (!csInfo) {
+            toastr.error('未找到该补充合同');
+            return
+        }
+        if (node.settle_code) {
+            toastr.warning('已结算的合同不能编辑补充合同');
+            return;
+        }
+        let only_sf_edit = false;
+        if (shenpi_status && (csInfo.status === auditConst.status.checking || csInfo.status === auditConst.status.checkNoPre) && csInfo.curAuditorIds && csInfo.curAuditorIds.length > 0 && csInfo.curAuditorIds.indexOf(user_id) > -1) {
+            only_sf_edit = true;
+        }
+        $('#cons-addsupplement .modal-title').text('编辑补充合同');
+        $('#cons-addsupplement input[name="csid"]').val(csid);
+        $('#add-contract-supplement').hide();
+        $('#save-contract-supplement').show();
+        setSupplementModalInfo(csInfo, only_sf_edit);
+        $('#cons-addsupplement').modal('show');
+    });
+
+    function setSupplementModalInfo(csInfo = null, only_sf_edit = false) {
+        $('#cons-addsupplement input[name="code"]').val(csInfo ? csInfo.code : '');
+        $('#cons-addsupplement input[name="name"]').val(csInfo ? csInfo.name : '');
+        $('#cons-addsupplement input[name="price"]').val(csInfo ? csInfo.price : '');
+        $('#cons-addsupplement input[name="party_a"]').val(csInfo ? csInfo.party_a : '');
+        $('#cons-addsupplement input[name="party_b"]').val(csInfo ? csInfo.party_b : '');
+        $('#cons-addsupplement input[name="sign_time"]').val(csInfo && csInfo.sign_time ? moment(csInfo.sign_time).format('YYYY-MM-DD') : '');
+        signTime.selectDate(csInfo && csInfo.sign_time ? new Date(csInfo.sign_time) : '');
+        $('#cons-addsupplement textarea[name="content"]').val(csInfo ? csInfo.content : '');
+        if (only_sf_edit) {
+            // 用readOnly
+            $('#cons-addsupplement input[name="code"]').attr('readonly', true);
+            $('#cons-addsupplement input[name="name"]').attr('readonly', true);
+            $('#cons-addsupplement input[name="party_a"]').attr('readonly', true);
+            $('#cons-addsupplement input[name="party_b"]').attr('readonly', true);
+            $('#cons-addsupplement input[name="sign_time"]').attr('readonly', true);
+            $('#cons-addsupplement input[name="address"]').attr('readonly', true);
+            $('#cons-addsupplement textarea[name="content"]').attr('readonly', true);
+        } else {
+            $('#cons-addsupplement input[name="code"]').removeAttr('readonly');
+            $('#cons-addsupplement input[name="name"]').removeAttr('readonly');
+            $('#cons-addsupplement input[name="party_a"]').removeAttr('readonly');
+            $('#cons-addsupplement input[name="party_b"]').removeAttr('readonly');
+            $('#cons-addsupplement input[name="sign_time"]').removeAttr('readonly');
+            $('#cons-addsupplement input[name="address"]').removeAttr('readonly');
+            $('#cons-addsupplement textarea[name="content"]').removeAttr('readonly');
+        }
+    }
+
+    // 上传附件
+    $('#cons-supplement-file input[type="file"]').change(function () {
+        const files = Array.from(this.files);
+        console.log(files);
+        const valiData = files.map(v => {
+            const ext = v.name.substring(v.name.lastIndexOf('.') + 1)
+            return {
+                size: v.size,
+                ext
+            }
+        })
+        const node = SpreadJsObj.getSelectObject(contractSheet);
+        if (!node || !node.c_code) {
+            toastr.warning('请选择合同再上传文件');
+            $('#cons-supplement-file input[type="file"]').val('');
+            return;
+        }
+        const csid = $('#cons-supplement-file input[name="csid"]').val();
+        const csInfo = _.find(contractSupplements, { id: parseInt(csid) });
+        if (!csInfo) {
+            toastr.warning('请选择补充合同再上传文件');
+            $('#cons-supplement-file input[type="file"]').val('');
+            return;
+        }
+        if (validateFiles(valiData)) {
+            if (files.length) {
+                const formData = new FormData()
+                files.forEach(file => {
+                    formData.append('name', file.name)
+                    formData.append('size', file.size)
+                    formData.append('file', file)
+                })
+                postDataWithFile(`${thisUrl}/${contractConst.typeMap[contract_type]}/${node.id}/supplement/${csInfo.id}/file/upload`, formData, function (result) {
+                    csInfo.files = result;
+                    contractTreeSpreadObj.openContractSupplementFiles(csInfo);
+                    $('#htsupplement-table tbody').find('tr[data-csid="' + csInfo.id + '"]').find('.files-num').text(result.length || '');
+                });
+            }
+        }
+        $('#cons-supplement-file input[type="file"]').val('');
+    });
+
+    $('body').on('click', '#cons-supplement-file .file-del', function () {
+        const node = SpreadJsObj.getSelectObject(contractSheet);
+        const csid = $('#cons-supplement-file input[name="csid"]').val();
+        const csInfo = _.find(contractSupplements, { id: parseInt(csid) });
+        if (!csInfo) {
+            toastr.warning('未选择补充合同');
+            return;
+        }
+        const fid = $(this).data('id');
+        deleteAfterHint(function () {
+            postData(`${thisUrl}/${contractConst.typeMap[contract_type]}/${node.id}/supplement/${csInfo.id}/file/delete`, { id: fid }, function (result) {
+                csInfo.files = result;
+                contractTreeSpreadObj.openContractSupplementFiles(csInfo);
+                $('#htsupplement-table tbody').find('tr[data-csid="' + csInfo.id + '"]').find('.files-num').text(result.length || '');
+            });
+        }, '确认删除该文件?');
+    });
+
     postData(window.location.pathname + '/load', {}, function (result) {
         const datas = result.contractTree.concat(result.contractList);
         for (const t of datas) {
@@ -2428,7 +2770,8 @@ $(document).ready(function() {
         postData(window.location.pathname + '/update', {postType: 'add-contract-pay', postData: { select: node.id, pay: data }}, function (result) {
             const refreshNode = contractTree.loadPostData(result.node);
             contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
-            contractTreeSpreadObj.setContractPays(result.pays, node);
+            // contractTreeSpreadObj.setContractPays(result.pays, node);
+            contractTreeSpreadObj.setContractListHtml('pay', node, result.pays);
             contractTreeSpreadObj.checkCloseStatus(node);
             contractTreeSpreadObj.setContract(contractSheet, false);
             // const selection = contractSheet.getSelections();
@@ -2482,7 +2825,8 @@ $(document).ready(function() {
         postData(window.location.pathname + '/update', {postType: 'save-contract-pay', postData: { select: node.id, pay: data }}, function (result) {
             const refreshNode = contractTree.loadPostData(result.node);
             contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
-            contractTreeSpreadObj.setContractPays(result.pays, node);
+            // contractTreeSpreadObj.setContractPays(result.pays, node);
+            contractTreeSpreadObj.setContractListHtml('pay', node, result.pays);
             contractTreeSpreadObj.setContract(contractSheet, false);
             // const selection = contractSheet.getSelections();
             // const sel = selection ? selection[0] : contractSheet.getSelections()[0];
@@ -2579,6 +2923,151 @@ $(document).ready(function() {
         contractTreeSpreadObj.openContractPayFiles(pay);
     });
 
+    $('#add-contract-supplement').click(function () {
+        const node = SpreadJsObj.getSelectObject(contractSheet);
+        if (!node || !node.c_code) {
+            toastr.error('请选择一个合同节点');
+            return;
+        }
+        if (!(node.uid === user_id || permission_edit_contract)) {
+            toastr.error('没有权限新增补充合同');
+            return;
+        }
+        if (!(!shenpi_status || (shenpi_status && node.status === auditConst.status.checked))) {
+            toastr.error('该合同审批状态未通过,不能添加');
+            return;
+        }
+        if (node.settle_code) {
+            toastr.error('该合同已结算,不能添加');
+            return;
+        }
+        const data = {
+            code: $('#cons-addsupplement input[name="code"]').val(),
+            name: $('#cons-addsupplement input[name="name"]').val(),
+            price: $('#cons-addsupplement input[name="price"]').val(),
+            party_a: $('#cons-addsupplement input[name="party_a"]').val(),
+            party_b: $('#cons-addsupplement input[name="party_b"]').val(),
+            sign_time: $('#cons-addsupplement input[name="sign_time"]').val() || null,
+            address: $('#cons-addsupplement input[name="address"]').val(),
+            content: $('#cons-addsupplement textarea[name="content"]').val(),
+        }
+        if (!judgeSupplements(data)) {
+            return;
+        }
+        console.log(node, data);
+        postData(window.location.pathname + '/update', {postType: 'add-contract-supplement', postData: { select: node.id, supplement: data }}, function (result) {
+            const refreshNode = contractTree.loadPostData(result.node);
+            contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
+            // contractTreeSpreadObj.setContractSupplements(result.supplements, node);
+            contractTreeSpreadObj.setContractListHtml('supplement', node, result.supplements);
+            contractTreeSpreadObj.checkCloseStatus(node);
+            contractTreeSpreadObj.setContract(contractSheet, false);
+            $('#cons-addsupplement').modal('hide');
+        })
+    });
+
+    $('#save-contract-supplement').click(function () {
+        const node = SpreadJsObj.getSelectObject(contractSheet);
+        if (!node || !node.c_code) {
+            toastr.error('请选择一个合同节点');
+            return;
+        }
+        if (node.settle_code) {
+            toastr.error('该合同已结算,不能修改');
+            return;
+        }
+        const csid = $('#cons-addsupplement input[name="csid"]').val();
+        const csInfo = _.find(contractSupplements, { id: parseInt(csid) });
+        if (!csInfo) {
+            toastr.error('未找到该补充合同');
+            return
+        }
+        let only_sf_edit = false;
+        if (shenpi_status && (csInfo.status === auditConst.status.checking || csInfo.status === auditConst.status.checkNoPre) && csInfo.curAuditorIds && csInfo.curAuditorIds.length > 0 && csInfo.curAuditorIds.indexOf(user_id) > -1) {
+            only_sf_edit = true;
+        }
+        if (!(csInfo.uid === user_id || only_sf_edit)) {
+            toastr.error('只能编辑自己创建的补充合同');
+            return;
+        }
+        const data = {
+            id: csInfo.id,
+            code: $('#cons-addsupplement input[name="code"]').val(),
+            name: $('#cons-addsupplement input[name="name"]').val(),
+            price: $('#cons-addsupplement input[name="price"]').val(),
+            party_a: $('#cons-addsupplement input[name="party_a"]').val(),
+            party_b: $('#cons-addsupplement input[name="party_b"]').val(),
+            sign_time: $('#cons-addsupplement input[name="sign_time"]').val() || null,
+            address: $('#cons-addsupplement input[name="address"]').val(),
+            content: $('#cons-addsupplement textarea[name="content"]').val(),
+        }
+        if (!judgeSupplements(data)) {
+            return;
+        }
+        console.log(node, data);
+        postData(window.location.pathname + '/update', {postType: 'save-contract-supplement', postData: { select: node.id, supplement: data }}, function (result) {
+            const refreshNode = contractTree.loadPostData(result.node);
+            contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
+            // contractTreeSpreadObj.setContractSupplements(result.supplements, node);
+            contractTreeSpreadObj.setContractListHtml('supplement', node, result.supplements);
+            contractTreeSpreadObj.setContract(contractSheet, false);
+            // const selection = contractSheet.getSelections();
+            // const sel = selection ? selection[0] : contractSheet.getSelections()[0];
+            // const row = sel ? sel.row : -1;
+            // contractTreeSpreadObj.setForeColor(contractSheet, row);
+            $('#cons-addsupplement').modal('hide');
+        })
+    });
+
+    function judgeSupplements(data) {
+        let flag = true;
+        if (!data.code) {
+            toastr.error('请输入编号');
+            return false;
+        }
+        if (!data.name) {
+            toastr.error('请输入名称');
+            return false;
+        }
+        console.log(data.price);
+        if (data.price === '') {
+            toastr.error('请输入金额');
+            return false;
+        }
+        // 金额只能输入数字
+        if (!/^\d+(\.\d+)?$/.test(data.price)) {
+            toastr.error('金额只能输入数字');
+            return false;
+        }
+        return flag;
+    }
+
+    $('body').on('click', '.open-supplement-files', function () {
+        const csid = $(this).attr('data-csid');
+        if (!csid) {
+            toastr.error('获取补充合同信息失败');
+            return;
+        }
+        const supplement = _.find(contractSupplements, { id: parseInt(csid) });
+        if (!supplement) {
+            toastr.error('获取补充合同信息失败');
+            return;
+        }
+        const node = SpreadJsObj.getSelectObject(contractSheet);
+        if (!node || !node.c_code) {
+            toastr.error('请选择一个合同节点');
+            return;
+        }
+        if (node.uid === user_id || supplement.uid === user_id || permission_att) {
+            $('#cons-supplement-file .upload-permission').show();
+        } else {
+            $('#cons-supplement-file .upload-permission').hide();
+        }
+        $('#cons-supplement-file').modal('show');
+        $('#cons-supplement-file input[name="csid"]').val(csid);
+        contractTreeSpreadObj.openContractSupplementFiles(supplement);
+    });
+
     $('#cons-close').on('show.bs.modal', function () {
         const node = SpreadJsObj.getSelectObject(contractSheet);
         if (!node || !node.c_code) {
@@ -2696,6 +3185,42 @@ $(document).ready(function() {
         return shenpi_html;
     }
 
+    function setSupplementShenpiHtml(pay, reload = false, contract = null) {
+        let shenpi_html = '';
+        if (shenpi_status) {
+            if (contract && contract.status !== auditConst.status.checked) {
+                shenpi_html += `<td class="text-secondary">合同未完成审批</td>`;
+                return shenpi_html;
+            }
+            shenpi_html += !reload ? `<td class="shenpi-td ${auditConst.auditProgressClass[pay.status]}">` : '';
+            if ((pay.status === auditConst.status.uncheck || pay.status === auditConst.status.checkNo) && pay.uid === user_id) {
+                shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="btn ${auditConst.statusButtonClass[pay.status]} btn-sm show-sub-sp">${auditConst.statusButton[pay.status]}</a>`;
+            } else if ((pay.status === auditConst.status.checking || pay.status === auditConst.status.checkNoPre) && pay.curAuditors && pay.curAuditors.findIndex(x => { return x.aid === user_id; }) >= 0) {
+                shenpi_html += `<a href="javascript: void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" data-operate="checked" data-title="审批通过" class="btn btn-success btn-sm mr-2 show-sp-list">审批通过</a>`;
+                shenpi_html += `<a href="javascript: void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" data-operate="checkNo" data-title="审批退回" class="btn btn-warning btn-sm show-sp-list">审批退回</a>`;
+            } else {
+                if (pay.status === auditConst.status.checked && pay.final_auditor_str) {
+                    shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="show-sp-list">${pay.final_auditor_str}</a>`;
+                } else if (pay.status === auditConst.status.checkNo && pay.curAuditors2 && pay.curAuditors2.length > 0) {
+                    if (pay.curAuditors2[0].audit_type === auditType.key.common) {
+                        shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="show-sp-list">${pay.curAuditors2[0].name}${pay.curAuditors2[0].role !== '' && pay.curAuditors2[0].role !== null ? '-' + pay.curAuditors2[0].role : ''}</a>`;
+                    } else {
+                        shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="show-sp-list">${transFormToChinese(pay.curAuditors2[0].audit_order) + '审'}</a>`;
+                    }
+                } else if (pay.curAuditors.length > 0) {
+                    if (pay.curAuditors[0].audit_type === auditType.key.common) {
+                        shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="show-sp-list">${pay.curAuditors[0].name}${pay.curAuditors[0].role !== '' && pay.curAuditors[0].role !== null ? '-' + pay.curAuditors[0].role : ''}</a>`;
+                    } else {
+                        shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="show-sp-list">${transFormToChinese(pay.curAuditors[0].audit_order) + '审'}</a>`;
+                    }
+                }
+                shenpi_html += ` ${auditConst.auditProgress[pay.status]}`;
+            }
+            shenpi_html += !reload ? '</td>' : '';
+        }
+        return shenpi_html;
+    }
+
     if (shenpi_status || pay_shenpi_status) {
         $('body').on('click', '.show-sub-sp', function () {
             console.log($(this).attr('node-cid'));

+ 340 - 0
app/public/js/cost_compare.js

@@ -0,0 +1,340 @@
+$(document).ready(() => {
+    autoFlashHeight();
+    const compareUtils = {
+        refreshSpreadSetting(roles, spreadSetting) {
+            spreadSetting.cols = [];
+            spreadSetting.fieldSufs = [];
+            for (const col of spreadSetting.baseCols) {
+                spreadSetting.cols.push(col);
+            }
+            for (const role of roles) {
+                spreadSetting.fieldSufs.push(role.active_order);
+                for (const ec of spreadSetting.extraCols) {
+                    const col = JSON.parse(JSON.stringify(ec));
+                    col.title = _.replace(col.title, '%s', role.name);
+                    col.field = col.field + '_' + role.active_order;
+                    spreadSetting.cols.push(col);
+                }
+            }
+        },
+        analysisCompareData(datas, roles, extraCols){
+            const findHis = function (role, history) {
+                let his = null;
+                for (const h of history) {
+                    if (h.audit_times < role.audit_times || (h.audit_times === role.audit_times && h.active_order <= role.active_order)) {
+                        his = h;
+                    } else {
+                        break;
+                    }
+                }
+                return his;
+            };
+            for (const d of datas) {
+                if (d.tree_id && !d.tree_is_leaf) continue;
+                if (!d.calc_his) d.calc_his = [];
+                d.calc_his.sort((x, y) => { return x.audit_times === y.audit_times ? x.active_order - y.active_order : x.audit_times - y.audit_times; });
+                for (const r of roles) {
+                    if (r.latest) {
+                        for (const col of extraCols){
+                            d[col.field + '_' + r.active_order] = d[col.field];
+                        }
+                    } else {
+                        const rHis = findHis(r, d.calc_his);
+                        if (rHis) {
+                            for (const col of extraCols){
+                                d[col.field + '_' + r.active_order] = rHis[col.field];
+                            }
+                        }
+                    }
+                }
+            }
+        },
+        filterSameRoles(roles) {
+            const result = [];
+            for (const af of roles) {
+                const naf = roles.find(x => { return x.audit_order === af.audit_order && x.active_order > af.active_order; });
+                if (!naf) result.push(af);
+            }
+            return result;
+        }
+    };
+    const converseSpreadSetting = function(setting) {
+        setting.baseCols = [];
+        setting.extraCols = [];
+        let firstExtraCol;
+        for (const col of setting.cols) {
+            if (calcFields.indexOf(col.field) < 0) {
+                if (firstExtraCol) continue;
+                col.rowSpan = (setting.headRows + 1) + '';
+                setting.baseCols.push(col);
+            } else {
+                if (!firstExtraCol) {
+                    firstExtraCol = col;
+                    col.title = '%s|' + col.title;
+                    col.rowSpan = '1|' + col.rowSpan;
+                } else {
+                    col.title = '|' + col.title;
+                    col.colSpan = '|' + col.colSpan;
+                    col.rowSpan = '|' + col.rowSpan;
+                }
+                setting.extraCols.push(col);
+            }
+        }
+        firstExtraCol.colSpan = setting.extraCols.length + '|' + firstExtraCol.colSpan;
+        setting.headRows = setting.headRows + 1;
+        setting.headRowHeight.unshift(setting.headRowHeight[0]);
+        setting.cols = [...setting.baseCols];
+        return setting;
+    };
+    class BillsObj {
+        constructor() {
+            this.spread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            this.treeSetting = {
+                id: 'tree_id',
+                pid: 'tree_pid',
+                order: 'tree_order',
+                level: 'tree_level',
+                isLeaf: 'tree_is_leaf',
+                fullPath: 'tree_full_path',
+                rootId: -1,
+                calcFields: ['yf_tp', 'sf_tp', 'pay_tp', 'cut_tp', 'yf_excl_tax_tp', 'sf_excl_tax_tp', 'end_yf_tp', 'end_sf_tp', 'end_pay_tp', 'end_cut_tp', 'end_yf_excl_tax_tp', 'end_sf_excl_tax_tp'],
+                keys: ['id', 'stage_id', 'tree_id'],
+            };
+            this.tree = createNewPathTree('ledger', this.treeSetting);
+            this.spreadSetting = converseSpreadSetting(bllsSpreadSetting);
+            sjsSettingObj.setFxTreeStyle(this.spreadSetting, sjsSettingObj.FxTreeStyle.phasePay);
+            this.ckBillsSpread = window.location.pathname + '-billsSelect';
+
+            this.initSpread();
+        }
+        initSpread() {
+            SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
+            this.spread.bind(spreadNS.Events.SelectionChanged, this.selectionChanged);
+            this.spread.bind(spreadNS.Events.topRowChanged, this.topRowChanged);
+        }
+        loadRelaData() {
+            SpreadJsObj.saveTopAndSelect(this.sheet, this.ckBillsSpread);
+            const select = SpreadJsObj.getSelectObject(this.sheet);
+            detailObj.loadDetailData(select);
+        }
+        initSpreadSetting(roles = []) {
+            compareUtils.refreshSpreadSetting(roles, this.spreadSetting);
+            SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
+        }
+        loadData(datas, roles) {
+            this.initSpreadSetting(roles);
+            compareUtils.analysisCompareData(datas, roles, this.spreadSetting.extraCols);
+            this.tree.loadDatas(datas);
+            this.tree.setting.calcFields = [];
+            for (const r of roles) {
+                for (const col of this.spreadSetting.extraCols) {
+                    this.tree.setting.calcFields.push(col.field + '_' + r.active_order);
+                }
+            }
+            treeCalc.calculateAll(this.tree);
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.tree);
+            SpreadJsObj.loadTopAndSelect(this.sheet, this.ckBillsSpread);
+        }
+        // 事件
+        selectionChanged(e, info) {
+            if (info.newSelections) {
+                if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                    billsObj.loadRelaData();
+                }
+            }
+        }
+        topRowChanged(e, info) {
+            SpreadJsObj.saveTopAndSelect(info.sheet, billsObj.ckBillsSpread);
+        }
+    }
+    const billsObj = new BillsObj();
+    class DetailObj {
+        constructor() {
+            this.spread = SpreadJsObj.createNewSpread($('#detail-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            this.data = createAncillaryGcl({ id: 'id', masterId: 'ledger_id', sort: [['d_order', 'asc']] });
+            this.dealSpreadSetting = converseSpreadSetting(dealSpreadSetting);
+            this.commonSpreadSetting = converseSpreadSetting(commonSpreadSetting);
+
+            this.initSpread();
+        }
+        initSpread() {
+            SpreadJsObj.initSheet(this.sheet, this.commonSpreadSetting);
+        }
+        refreshSpreadSetting(roles, spreadSetting) {
+            spreadSetting.cols = [];
+            spreadSetting.fieldSufs = [];
+            for (const col of spreadSetting.baseCols) {
+                spreadSetting.cols.push(col);
+            }
+            for (const role of roles) {
+                spreadSetting.fieldSufs.push(role.active_order);
+                for (const ec of spreadSetting.extraCols) {
+                    const col = JSON.parse(JSON.stringify(ec));
+                    col.title = _.replace(col.title, '%s', role.name);
+                    col.field = col.field + '_' + role.active_order;
+                    spreadSetting.cols.push(col);
+                }
+            }
+        }
+        initSpreadSetting(roles = []) {
+            this.refreshSpreadSetting(roles, this.commonSpreadSetting);
+            this.refreshSpreadSetting(roles, this.dealSpreadSetting);
+            SpreadJsObj.initSheet(this.sheet, this.commonSpreadSetting);
+        }
+        loadData(datas, roles) {
+            this.initSpreadSetting(roles);
+            compareUtils.analysisCompareData(datas, roles, this.dealSpreadSetting.extraCols);
+            this.data.loadDatas(datas);
+            this.loadDetailData(SpreadJsObj.getSelectObject(billsObj.sheet));
+        }
+        refreshSheet() {
+            if (this.sheet.zh_data.length === 0) {
+                this.reloadDetailData();
+            } else {
+                SpreadJsObj.reLoadSheetData(this.sheet);
+            }
+        }
+        reloadDetailData() {
+            const data = this.billsNode ? this.data.getPartData(this.billsNode.id) || [] : [];
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Data, data);
+        }
+        loadDetailData(bills) {
+            detailObj.billsNode = bills;
+            const spreadSetting = bills && bills.is_deal ? this.dealSpreadSetting : this.commonSpreadSetting;
+            SpreadJsObj.initSheet(this.sheet, spreadSetting);
+            this.reloadDetailData();
+            SpreadJsObj.locateRow(this.sheet, 0, false);
+        }
+    }
+    const detailObj = new DetailObj();
+
+    const searchObj = $.ledgerSearch({
+        selector: '#search',
+        searchRangeStr: '编号/名称',
+        ledger: {
+            billsTree: billsObj.tree,
+            getLedgerPos: function(node) { return detailObj.data.getPartData(node.id); },
+            posId: 'id',
+        },
+        resultSpreadSetting: {
+            cols: [
+                {title: '编号', field: 'code', hAlign: 0, width: 90, formatter: '@'},
+                {title: '名称', field: 'name', width: 150, hAlign: 0, formatter: '@'},
+                {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@'},
+            ],
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            headColWidth: [30],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            selectedBackColor: '#fffacd',
+            readOnly: true,
+        },
+        locate: function(cur) {
+            if (!cur.lid) return;
+
+            SpreadJsObj.locateTreeNode(billsObj.sheet, cur.lid, true);
+            billsObj.loadRelaData();
+            if (cur.pid) {
+                const pIndex = detailObj.sheet.zh_data.findIndex(x => { return x.id === cur.pid; });
+                if (pIndex >= 0) SpreadJsObj.locateRow(detailObj.sheet, pIndex);
+            }
+        },
+    });
+    // 加载数据
+    postData('load', { filter: 'billsCompare;detailCompare;auditFlow' }, function(result) {
+        const filter = true;
+        const roles = filter ? compareUtils.filterSameRoles(result.auditFlow) : result.auditFlow;
+        billsObj.loadData(result.billsCompare, roles);
+        detailObj.loadData(result.detailCompare, roles);
+    });
+    // 展开收起标准清单
+    $('a', '#side-menu').bind('click', function (e) {
+        e.preventDefault();
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        // 展开工具栏、切换标签
+        if (!tab.hasClass('active')) {
+            // const close = $('.active', '#side-menu').length === 0;
+            $('a', '#side-menu').removeClass('active');
+            $('.tab-content .tab-select-show.tab-pane.active').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            // $('.tab-content .tab-pane').removeClass('active');
+            showSideTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#search') {
+                searchObj.spread.refresh();
+            }
+        } else { // 收起工具栏
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        billsObj.spread.refresh();
+        detailObj.spread.refresh();
+    });
+
+    $.divResizer({
+        select: '#detail-spr',
+        callback: function () {
+            billsObj.spread.refresh();
+            detailObj.spread.refresh();
+        }
+    });
+    // 工具栏spr
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            billsObj.spread.refresh();
+            detailObj.spread.refresh();
+            searchObj.spread.refresh();
+        }
+    });
+    // 导航Menu
+    $.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();
+            billsObj.spread.refresh();
+            detailObj.spread.refresh();
+        }
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            if (!sheet.zh_tree) return;
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
+        });
+    })('a[name=showLevel]', billsObj.sheet);
+});

+ 186 - 0
app/public/js/cost_gather.js

@@ -0,0 +1,186 @@
+$(document).ready(() => {
+    autoFlashHeight();
+    const ledgerGatherSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 230, formatter: '@', cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', cellType: 'autoTip'},
+        ],
+        baseCols: [
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 230, formatter: '@', cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', cellType: 'autoTip'},
+        ],
+        extraCols: [
+            {title: '%s|付款金额', colSpan: '4|1', rowSpan: '1|1', field: 'pay_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|扣款金额', colSpan: '|1', rowSpan: '|1', field: 'cut_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|应付金额', colSpan: '|1', rowSpan: '|1', field: 'yf_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    sjsSettingObj.setFxTreeStyle(ledgerGatherSpreadSetting, sjsSettingObj.FxTreeStyle.phasePay);
+
+    const ledgerGatherSpread = SpreadJsObj.createNewSpread($('#ledger-gather-spread')[0]);
+    const ledgerGatherSheet = ledgerGatherSpread.getActiveSheet();
+    SpreadJsObj.initSheet(ledgerGatherSheet, ledgerGatherSpreadSetting);
+
+    const ledgerGatherObj = {
+        tree: createNewPathTree('ledger', {
+            id: 'tree_id', pid: 'tree_pid', order: 'tree_order',
+            level: 'tree_level', isLeaf: 'tree_is_leaf', fullPath: 'tree_full_path',
+            rootId: -1, calcFields: [],
+        }),
+        stages: [],
+        splitStages (gatherType, gatherStageIds) {
+            const gatherStages = this.stages.filter(x => { return gatherStageIds.indexOf(x.id) >= 0; });
+            gatherStages.sort((a, b) => { return a.stage_order - b.stage_order; });
+            const result = { main: gatherStages[gatherStages.length - 1], gatherPart: [] };
+            for (const gs of gatherStages) {
+                if (gatherType === 'stage') {
+                    result.gatherPart.push({title: `第${gs.stage_order}期(${gs.stage_date})`, stages: [gs]});
+                } else {
+                    let title;
+                    if (gatherType === 'month') title = `${gs.stage_date}`;
+                    if (gatherType === 'quarter') title = `${gs.gather_year} 第${gs.gather_quarter}季度`;
+                    if (gatherType === 'year') title = `${gs.gather_year}`;
+
+                    let gp = result.gatherPart.find(x => { return x.title === title; });
+                    if (!gp) {
+                        gp = { title, stages: [] };
+                        result.gatherPart.push(gp);
+                    }
+                    gp.stages.push(gs);
+                }
+            }
+            return result;
+        },
+        gather (gatherType, gatherStageIds) {
+            if (gatherStageIds.length === 0) return;
+            const gatherInfo = this.splitStages(gatherType, gatherStageIds);
+
+            this.tree.setting.calcFields.length = 0;
+            this.tree.loadDatas(gatherInfo.main.detail.bills);
+            ledgerGatherSpreadSetting.cols.length = 0;
+            ledgerGatherSpreadSetting.cols.push(...ledgerGatherSpreadSetting.baseCols);
+            for (const [i, gp] of gatherInfo.gatherPart.entries()) {
+                const loadFields = [];
+                for (const ec of ledgerGatherSpreadSetting.extraCols) {
+                    const col = JSON.parse(JSON.stringify(ec));
+                    col.title = col.title.replace('%s', gp.title);
+                    loadFields.push({ sf: col.field, tf: col.field + '_' + i});
+                    col.field = col.field + '_' + i;
+                    ledgerGatherSpreadSetting.cols.push(col);
+                    this.tree.setting.calcFields.push(col.field);
+                    this.tree.nodes.forEach(x => { x[col.field] = 0; });
+                }
+                for (const s of gp.stages) {
+                    for (b of s.detail.bills) {
+                        const node = this.tree.nodes.find(x => { return x.cost_id === b.cost_id; });
+                        if (!node) continue;
+                        for (const lf of loadFields) {
+                            node[lf.tf] = ZhCalc.add(node[lf.tf], b[lf.sf]);
+                        }
+                    }
+                }
+            }
+            treeCalc.calculateAll(this.tree);
+            SpreadJsObj.initSheet(ledgerGatherSheet, ledgerGatherSpreadSetting);
+            SpreadJsObj.loadSheetData(ledgerGatherSheet, SpreadJsObj.DataType.Tree, this.tree);
+            SpreadJsObj.locateRow(ledgerGatherSheet, 0);
+        },
+        loadStages (stages) {
+            for (const s of stages) {
+                s.gather_stage = s.stage_order;
+                s.gather_month = s.stage_date;
+                s.gather_time = moment(new Date(s.stage_date));
+                s.gather_quarter = s.gather_time.quarter();
+                s.gather_year = s.gather_time.year();
+                this.stages.push(s);
+            }
+        },
+        exportExcel() {
+            if (!this.tree || this.tree.nodes.length === 0) {
+                toastr.warning('无可导出数据');
+                return;
+            }
+            SpreadExcelObj.exportSimpleXlsxSheet(ledgerGatherSpreadSetting, this.tree.nodes, "成本管理-汇总分析.xlsx");
+        }
+    };
+
+    $('#select-qi-all').click(function() {
+        const check = this.checked;
+        $('input', 'tr[stage-id]').each((i, x) => {
+            x.checked = check;
+        })
+    });
+    $('#select-qi-ok').click(function() {
+        let loadData = [], gatherData = [], trs = $('tr[stage-id]');
+        for (let order = 0, iLength = trs.length; order < iLength; order++) {
+            const tr = trs[order];
+            if ($('input[type=checkbox]', tr)[0].checked) {
+                const sid = tr.getAttribute('stage-id');
+                const exist = ledgerGatherObj.stages.find(x => { return x.id === sid; });
+                if (!exist) loadData.push(sid);
+                gatherData.push(sid);
+            }
+        }
+        const gatherType = $('input[name=gather-type]:checked').val();
+        if (loadData.length > 0) {
+            postData(window.location.pathname + '/load', {stages: loadData}, function (result) {
+                ledgerGatherObj.loadStages(result.stages);
+                ledgerGatherObj.gather(gatherType, gatherData);
+                $('#select-qi').modal('hide');
+            }, null, true);
+        } else {
+            ledgerGatherObj.gather(gatherType, gatherData);
+            $('#select-qi').modal('hide');
+        }
+    });
+
+    $('#export-excel').click(function() {
+        ledgerGatherObj.exportExcel();
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            if (!sheet.zh_tree) return;
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+            }
+        });
+    })('a[name=showLevel]', ledgerGatherSheet);
+    $.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');
+                $('.c-body table thead').css('left', '56px');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+                $('.c-body table thead').css('left', '176px');
+            }
+            autoFlashHeight();
+            ledgerGatherSpread.refresh();
+        }
+    });
+});

+ 104 - 19
app/public/js/cost_stage_analysis.js

@@ -1,9 +1,3 @@
-function getTenderId() {
-    return window.location.pathname.split('/')[2];
-}
-const invalidFields = {
-    parent: ['cur_qty', 'cur_tp', 'unit_price'],
-};
 function transExpr(expr) {
     return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
 }
@@ -31,6 +25,7 @@ $(document).ready(function() {
             this.tree = createNewPathTree('ledger', this.treeSetting);
             this.spreadSetting = bllsSpreadSetting;
             this.spreadSetting.readOnly = readOnly;
+            this.spreadSetting.tipsSum = true;
             sjsSettingObj.setFxTreeStyle(this.spreadSetting, sjsSettingObj.FxTreeStyle.phasePay);
 
             this.ckBillsSpread = window.location.pathname + '-billsSelect';
@@ -58,6 +53,7 @@ $(document).ready(function() {
             $('a[name="base-opr"]').click(function () {
                 self.baseOpr(this.getAttribute('type'));
             });
+            $('#bills-spread').focusin(this.focusIn);
         }
         refreshOperationValid() {
             const setObjEnable = function (obj, enable) {
@@ -87,7 +83,7 @@ $(document).ready(function() {
                 invalidAll();
                 return;
             }
-            let last = first, sameParent = true, nodeUsed = this.checkNodeUsed(tree, first);
+            let last = first, sameParent = true, specNode = !!first.node_type, nodeUsed = this.checkNodeUsed(tree, first);
             if (sel.rowCount > 1 && first) {
                 for (let r = 1; r < sel.rowCount; r++) {
                     const rNode = tree.nodes[sel.row + r];
@@ -95,6 +91,7 @@ $(document).ready(function() {
                         sameParent = false;
                         break;
                     }
+                    if (!specNode) specNode = !!rNode.node_type;
                     nodeUsed = nodeUsed || this.checkNodeUsed(tree, rNode);
                     if (rNode.tree_level > first.tree_level) continue;
                     if ((rNode.tree_level < first.tree_level) || (rNode.tree_level === first.tree_level && rNode.tree_pid !== first.tree_pid)) {
@@ -110,17 +107,18 @@ $(document).ready(function() {
             const isFixed = first.tree_level === 1 || [2,3,4].indexOf(topParent.node_type) >= 0;
 
             setObjEnable($('a[name=base-opr][type=add]'), valid && first && !isFixed);
-            setObjEnable($('a[name=base-opr][type=delete]'), valid && first && sameParent && !isFixed && !first.node_type);
+            setObjEnable($('a[name=base-opr][type=delete]'), valid && first && sameParent && !isFixed && !specNode);
             setObjEnable($('a[name=base-opr][type=up-move]'), valid && first && sameParent && preNode && !isFixed);
             setObjEnable($('a[name=base-opr][type=down-move]'), valid && first && sameParent  && !tree.isLastSibling(last) && !isFixed);
             setObjEnable($('a[name=base-opr][type=up-level]'), valid && first && sameParent && tree.getParent(first) && !nodeUsed && first.tree_level > 2 && !isFixed);
             setObjEnable($('a[name=base-opr][type=down-level]'), valid && first && sameParent && preNode && !isFixed && !preNode.node_type);
         }
-        loadRelaData() {
+        loadRelaData(loadAtt = true) {
             this.refreshOperationValid();
             SpreadJsObj.saveTopAndSelect(this.sheet, this.ckBillsSpread);
             const select = SpreadJsObj.getSelectObject(this.sheet);
             detailObj.loadDetailData(select);
+            if (loadAtt) costFile.getCurAttHtml(select);
         }
         refreshTree(data) {
             const sheet = this.sheet;
@@ -272,6 +270,7 @@ $(document).ready(function() {
                             sheet.setSelection(sel.row, sel.col, 1, sel.colCount);
                         }
                         self.refreshOperationValid();
+                        costFile.deleteFileByNodeId(result.delete.map(x => { return x.id; }));
                     });
                 });
             } else {
@@ -281,20 +280,25 @@ $(document).ready(function() {
                     if (['up-move', 'down-move'].indexOf(type) > -1) {
                         if (sel) {
                             sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
-                            // SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(node)]);
+                            SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(node)]);
                         }
                     } else if (type === 'add') {
                         const sel = sheet.getSelections()[0];
                         if (sel) {
                             sheet.setSelection(tree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
-                            // SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(refreshData.create[0])]);
+                            SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(refreshData.create[0])]);
                         }
+                        billsObj.loadRelaData();
                     }
                     self.refreshOperationValid();
                 });
             }
         }
         // 事件
+        focusIn() {
+            const select = SpreadJsObj.getSelectObject(billsObj.sheet);
+            costFile.getCurAttHtml(select);
+        }
         selectionChanged(e, info) {
             if (info.newSelections) {
                 if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
@@ -451,7 +455,25 @@ $(document).ready(function() {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
-                    data[colSetting.field] = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    if (datas.length > 0) if (datas[0][colSetting.field] === undefined) continue;
+
+                    const newText = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    if (colSetting.type === 'Number') {
+                        const num = _.toNumber(newText);
+                        if (!_.isFinite(num)) {
+                            try {
+                                data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(newText));
+                            } catch (err) {
+                                toastr.error('粘贴的计算式非法');
+                                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                                return;
+                            }
+                        } else {
+                            data[colSetting.field] = num;
+                        }
+                    } else {
+                        data[colSetting.field] = newText;
+                    }
                     bPaste = true;
                 }
                 if (bPaste) {
@@ -481,13 +503,17 @@ $(document).ready(function() {
             this.data = createAncillaryGcl({ id: 'id', masterId: 'ledger_id', sort: [['d_order', 'asc']] });
             this.dealSpreadSetting = dealSpreadSetting;
             this.dealSpreadSetting.readOnly = readOnly;
+            this.dealSpreadSetting.tipsSum = true;
             this.commonSpreadSetting = commonSpreadSetting;
             this.commonSpreadSetting.readOnly = readOnly;
+            this.commonSpreadSetting.tipsSum = true;
 
             this.initSpread();
+            this.initOtherEvent();
         }
         initSpread() {
             SpreadJsObj.initSheet(this.sheet, this.commonSpreadSetting);
+            this.spread.bind(spreadNS.Events.SelectionChanged, this.selectionChanged);
             this.spread.bind(spreadNS.Events.ClipboardChanging, function (e, info) {
                 const copyText = SpreadJsObj.getFilterCopyText(info.sheet);
                 SpreadJsObj.Clipboard.setCopyData(copyText);
@@ -497,6 +523,9 @@ $(document).ready(function() {
             this.spread.bind(spreadNS.Events.ClipboardPasting, this.clipboardPasting);
             SpreadJsObj.addDeleteBind(this.spread, this.deletePress);
         }
+        initOtherEvent() {
+            $('#detail-spread').focusin(this.focusIn);
+        }
         loadData(datas) {
             this.data.loadDatas(datas);
             this.loadDetailData(SpreadJsObj.getSelectObject(billsObj.sheet));
@@ -523,9 +552,23 @@ $(document).ready(function() {
             }
             SpreadJsObj.initSheet(this.sheet, spreadSetting);
             this.reloadDetailData();
-            SpreadJsObj.locateRow(this.sheet, 0);
+            SpreadJsObj.locateRow(this.sheet, 0, false);
+        }
+        loadRelaData() {
+            const select = SpreadJsObj.getSelectObject(this.sheet);
+            if (select) costFile.getCurAttHtml(this.billsNode, select);
         }
         // 事件
+        focusIn() {
+            detailObj.loadRelaData();
+        }
+        selectionChanged(e, info) {
+            if (info.newSelections) {
+                if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                    detailObj.loadRelaData();
+                }
+            }
+        }
         editEnded(e, info) {
             const setting = info.sheet.zh_setting;
             if (!setting) return;
@@ -548,11 +591,16 @@ $(document).ready(function() {
                 if (col.type === 'Number') {
                     const num = _.toNumber(newText);
                     if (!_.isFinite(num)) {
-                        toastr.error('输入的数字非法');
-                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        return;
+                        try {
+                            updateData[col.field] = ZhCalc.mathCalcExpr(transExpr(newText));
+                        } catch (err) {
+                            toastr.error('输入的计算式非法');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                    } else {
+                        updateData[col.field] = num;
                     }
-                    updateData[col.field] = num;
                 } else {
                     updateData[col.field] = newText;
                 }
@@ -628,6 +676,8 @@ $(document).ready(function() {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
+                    if (!info.sheet.zh_setting.emptyRows && !node) continue;
+
                     const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
                     if (colSetting.type === 'Number') {
                         const num = _.toNumber(value);
@@ -739,7 +789,7 @@ $(document).ready(function() {
                 const tree = sheet.zh_tree;
                 if (!tree) return true;
                 const first = sheet.zh_tree.nodes[row];
-                let last = first, sameParent = true, nodeUsed = billsObj.checkNodeUsed(tree, first);
+                let last = first, sameParent = true, specNode = !!first.node_type, nodeUsed = billsObj.checkNodeUsed(tree, first);
                 if (sel.rowCount > 1 && first) {
                     for (let r = 1; r < sel.rowCount; r++) {
                         const rNode = tree.nodes[sel.row + r];
@@ -759,7 +809,7 @@ $(document).ready(function() {
                 const valid = !sheet.zh_setting.readOnly;
                 const topParent = tree.getTopParent(first);
                 const isFixed = first.tree_level === 1 || [2,3,4].indexOf(topParent.node_type) >= 0;
-                return !valid || !first || !sameParent || nodeUsed || isFixed;
+                return !valid || !first || !sameParent || nodeUsed || isFixed || specNode;
             }
         };
         billsContextMenuOptions.items.batchInsert = {
@@ -859,10 +909,45 @@ $(document).ready(function() {
             }
         },
     });
+    const costFile = $.ledger_att({
+        selector: '#fujian',
+        key: 'id',
+        subKey: 'id',
+        masterKey: 'rela_id',
+        subMasterKey: 'rela_sub_id',
+        uploadUrl: 'file/upload',
+        deleteUrl: 'file/delete',
+        checked: costStageComplete,
+        zipName: `附件.zip`,
+        readOnly: false,
+        fileIdType: 'string',
+        fileInfo: {
+            user_name: 'user_name',
+            user_id: 'user_id',
+            create_time: 'create_time',
+        },
+        getCurHint: function(node) {
+            return`${node.name || ''}`;
+        },
+        locate: function (att) {
+            if (!att) return;
+            SpreadJsObj.locateTreeNode(billsObj.sheet, att.node.tree_id, true);
+            detailObj.loadDetailData(att.node);
+            if (att.sub_node) SpreadJsObj.locateDataBy(detailObj.sheet, n => { return n.id === att.sub_node.id; });
+            costFile.getCurAttHtml(att.node, att.sub_node);
+        }
+    });
     // 加载数据
     postData('load', { filter: 'bills;detail;att;tags' }, function(result) {
         billsObj.loadData(result.bills);
         detailObj.loadData(result.detail);
+
+        for (const f of result.att) {
+            f.node = billsObj.tree.datas.find(x => { return x.id === f.rela_id; });
+            f.sub_node = f.rela_sub_id ? detailObj.data.getItem(f.rela_sub_id) : null;
+        }
+        costFile.loadDatas(result.att);
+        costFile.getCurAttHtml(SpreadJsObj.getSelectObject(billsObj.sheet));
     });
 
     // 展开收起标准清单

+ 55 - 30
app/public/js/cost_stage_book.js

@@ -1,9 +1,3 @@
-function getTenderId() {
-    return window.location.pathname.split('/')[2];
-}
-const invalidFields = {
-    parent: ['cur_qty', 'cur_tp', 'unit_price'],
-};
 function transExpr(expr) {
     return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
 }
@@ -22,25 +16,30 @@ $(document).ready(function() {
                 isLeaf: 'tree_is_leaf',
                 fullPath: 'tree_full_path',
                 rootId: -1,
-                calcFields: ['sf_tp', 'in_tp', 'in_excl_tax_tp'],
+                calcFields: ['yf_tp', 'sf_tp', 'end_yf_tp', 'end_sf_tp', 'in_tp', 'in_excl_tax_tp', 'end_in_tp', 'end_in_excl_tax_tp'],
                 keys: ['id', 'stage_id', 'tree_id'],
             };
             this.tree = createNewPathTree('ledger', this.treeSetting);
             this.spreadSetting = {
                 cols: [
-                    {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 230, formatter: '@', cellType: 'tree', readOnly: true},
-                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 185, formatter: '@', cellType: 'autoTip', readOnly: true},
-                    {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit', readOnly: true},
-                    {title: '税率(%)', colSpan: '1', rowSpan: '1', field: 'tax', hAlign: 2, width: 80, type: 'Number', readOnly: true},
-                    {title: '实付金额', colSpan: '1', rowSpan: '1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
-                    {title: '入账金额', colSpan: '1', rowSpan: '1', field: 'in_tp', hAlign: 2, width: 80, type: 'Number'},
-                    {title: '入账金额(不含税)', colSpan: '1', rowSpan: '1', field: 'in_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
-                    {title: '本期批注', colSpan: '1', rowSpan: '1', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
-                    {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 120, formatter: '@', cellType: 'ellipsisAutoTip'},
+                    {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 230, formatter: '@', cellType: 'tree', readOnly: true},
+                    {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', cellType: 'autoTip', readOnly: true},
+                    {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit', readOnly: true},
+                    {title: '税率(%)', colSpan: '1', rowSpan: '2', field: 'tax', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+                    {title: '本期|应付金额', colSpan: '4|1', rowSpan: '1|1', field: 'yf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+                    {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+                    {title: '|入账金额', colSpan: '|1', rowSpan: '|1', field: 'in_tp', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '|入账金额\n(不含税)', colSpan: '|1', rowSpan: '|1', field: 'in_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
+                    {title: '截止本期|应付金额', colSpan: '4|1', rowSpan: '1|1', field: 'end_yf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+                    {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'end_sf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+                    {title: '|入账金额', colSpan: '|1', rowSpan: '|1', field: 'end_in_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+                    {title: '|入账金额\n(不含税)', colSpan: '|1', rowSpan: '|1', field: 'end_in_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+                    {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
+                    {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 120, formatter: '@', cellType: 'ellipsisAutoTip'},
                 ],
                 emptyRows: 0,
-                headRows: 1,
-                headRowHeight: [32],
+                headRows: 2,
+                headRowHeight: [36, 36],
                 defaultRowHeight: 21,
                 headerFont: '12px 微软雅黑',
                 font: '12px 微软雅黑',
@@ -49,6 +48,7 @@ $(document).ready(function() {
                     key: 'cost_book',
                     colWidth: true,
                 },
+                tipsSum: true,
             };
             sjsSettingObj.setFxTreeStyle(this.spreadSetting, sjsSettingObj.FxTreeStyle.phasePay);
 
@@ -349,7 +349,25 @@ $(document).ready(function() {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
-                    data[colSetting.field] = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    if (datas.length > 0) if (datas[0][colSetting.field] === undefined) continue;
+
+                    const newText = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    if (colSetting.type === 'Number') {
+                        const num = _.toNumber(newText);
+                        if (!_.isFinite(num)) {
+                            try {
+                                data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(newText));
+                            } catch (err) {
+                                toastr.error('粘贴的计算式非法');
+                                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                                return;
+                            }
+                        } else {
+                            data[colSetting.field] = num;
+                        }
+                    } else {
+                        data[colSetting.field] = newText;
+                    }
                     bPaste = true;
                 }
                 if (bPaste) {
@@ -382,11 +400,10 @@ $(document).ready(function() {
                     {title: '合同编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 160, formatter: '@', readOnly: true},
                     {title: '合同名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 160, formatter: '@', readOnly: true},
                     {title: '乙方', colSpan: '1', rowSpan: '1', field: 'party_b', hAlign: 0, width: 150, formatter: '@', readOnly: true},
-                    {title: '合同金额', colSpan: '1', rowSpan: '1', field: 'deal_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
                     {title: '税率(%)', colSpan: '1', rowSpan: '1', field: 'tax', hAlign: 2, width: 80, type: 'Number', readOnly: true},
                     {title: '实付金额', colSpan: '1', rowSpan: '1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
                     {title: '入账金额', colSpan: '1', rowSpan: '1', field: 'in_tp', hAlign: 2, width: 80, type: 'Number'},
-                    {title: '入账金额(不含税)', colSpan: '1', rowSpan: '1', field: 'in_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+                    {title: '入账金额(不含税)', colSpan: '1', rowSpan: '1', field: 'in_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
                     {title: '本期批注', colSpan: '1', rowSpan: '1', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
                     {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 120, formatter: '@', cellType: 'ellipsisAutoTip'},
                 ],
@@ -401,6 +418,7 @@ $(document).ready(function() {
                     key: 'cost_book_deal',
                     colWidth: true,
                 },
+                tipsSum: true,
             };
             this.commonSpreadSetting = {
                 cols: [
@@ -409,7 +427,7 @@ $(document).ready(function() {
                     {title: '税率(%)', colSpan: '1', rowSpan: '1', field: 'tax', hAlign: 2, width: 80, type: 'Number', readOnly: true},
                     {title: '实付金额', colSpan: '1', rowSpan: '1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
                     {title: '入账金额', colSpan: '1', rowSpan: '1', field: 'in_tp', hAlign: 2, width: 80, type: 'Number'},
-                    {title: '入账金额(不含税)', colSpan: '1', rowSpan: '1', field: 'in_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+                    {title: '入账金额(不含税)', colSpan: '1', rowSpan: '1', field: 'in_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
                     {title: '本期批注', colSpan: '1', rowSpan: '1', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
                     {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 120, formatter: '@', cellType: 'ellipsisAutoTip'},
                 ],
@@ -424,6 +442,7 @@ $(document).ready(function() {
                     key: 'cost_book_detail',
                     colWidth: true,
                 },
+                tipsSum: true,
             };
 
             this.initSpread();
@@ -470,7 +489,7 @@ $(document).ready(function() {
             }
             SpreadJsObj.initSheet(this.sheet, spreadSetting);
             this.reloadDetailData();
-            SpreadJsObj.locateRow(this.sheet, 0);
+            SpreadJsObj.locateRow(this.sheet, 0, false);
         }
         loadRelaData() {
             const select = SpreadJsObj.getSelectObject(this.sheet);
@@ -508,12 +527,17 @@ $(document).ready(function() {
                 const updateData = { book_id: detailData.book_id, detail_id: detailData.id, ledger_id: detailData.ledger_id };
                 if (col.type === 'Number') {
                     const num = _.toNumber(newText);
-                    if (!_.isFinite(num)) {
-                        toastr.error('输入的数字非法');
-                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        return;
+                    if (_.isFinite(num)) {
+                        updateData[col.field] = num;
+                    } else {
+                        try {
+                            updateData[col.field] = ZhCalc.mathCalcExpr(transExpr(newText));
+                        } catch(err) {
+                            toastr.error('输入的表达式非法');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
                     }
-                    updateData[col.field] = num;
                 } else {
                     updateData[col.field] = newText;
                 }
@@ -574,7 +598,7 @@ $(document).ready(function() {
             let filterRow = 0;
             for (let iRow = 0; iRow < info.cellRange.rowCount; iRow ++) {
                 const curRow = info.cellRange.row + iRow;
-                const node = tree.nodes[curRow];
+                const node = info.sheet.zh_data[curRow];
                 if (!node) continue;
 
                 let bPaste = false;
@@ -582,8 +606,9 @@ $(document).ready(function() {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
-                    const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    if (datas.length > 0) if (datas[0][colSetting.field] === undefined) continue;
 
+                    const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
                     if (colSetting.type === 'Number') {
                         const num = _.toNumber(value);
                         if (num) {

+ 70 - 35
app/public/js/cost_stage_ledger.js

@@ -1,9 +1,3 @@
-function getTenderId() {
-    return window.location.pathname.split('/')[2];
-}
-const invalidFields = {
-    parent: ['cur_qty', 'cur_tp', 'unit_price'],
-};
 function transExpr(expr) {
     return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
 }
@@ -22,28 +16,34 @@ $(document).ready(function() {
                 isLeaf: 'tree_is_leaf',
                 fullPath: 'tree_full_path',
                 rootId: -1,
-                calcFields: ['yf_tp', 'sf_tp', 'pay_tp', 'cut_tp'],
+                calcFields: ['yf_tp', 'sf_tp', 'pay_tp', 'cut_tp', 'yf_excl_tax_tp', 'sf_excl_tax_tp', 'end_yf_tp', 'end_sf_tp', 'end_pay_tp', 'end_cut_tp', 'end_yf_excl_tax_tp', 'end_sf_excl_tax_tp'],
                 keys: ['id', 'stage_id', 'tree_id'],
             };
             this.tree = createNewPathTree('ledger', this.treeSetting);
             this.spreadSetting = {
                 cols: [
-                    {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 230, formatter: '@', cellType: 'tree'},
-                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 185, formatter: '@', cellType: 'autoTip'},
-                    {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
-                    {title: '税率(%)', colSpan: '1', rowSpan: '1', field: 'tax', hAlign: 2, width: 80, type: 'Number'},
-                    {title: '付款金额', colSpan: '1', rowSpan: '1', field: 'pay_tp', hAlign: 2, width: 80, type: 'Number'},
-                    {title: '扣款金额', colSpan: '1', rowSpan: '1', field: 'cut_tp', hAlign: 2, width: 80, type: 'Number'},
-                    {title: '应付金额', colSpan: '1', rowSpan: '1', field: 'yf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
-                    {title: '实付金额', colSpan: '1', rowSpan: '1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number'},
-                    // {title: '应付金额(无税)', colSpan: '1', rowSpan: '1', field: 'yf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
-                    // {title: '实付金额(无税)', colSpan: '1', rowSpan: '1', field: 'sf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
-                    {title: '本期批注', colSpan: '1', rowSpan: '1', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
-                    {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 120, formatter: '@', cellType: 'ellipsisAutoTip'},
+                    {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 230, formatter: '@', cellType: 'tree'},
+                    {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', cellType: 'autoTip'},
+                    {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+                    {title: '税率(%)', colSpan: '1', rowSpan: '2', field: 'tax', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '本期|付款金额', colSpan: '4|1', rowSpan: '1|1', field: 'pay_tp', hAlign: 2, width: 80, type: 'Number', visible: colSet.cur ? true : false},
+                    {title: '|扣款金额', colSpan: '|1', rowSpan: '|1', field: 'cut_tp', hAlign: 2, width: 80, type: 'Number', visible: colSet.cur ? true : false},
+                    {title: '|应付金额', colSpan: '|1', rowSpan: '|1', field: 'yf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, visible: colSet.cur ? true : false},
+                    {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number', visible: colSet.cur ? true : false},
+                    {title: '本期(不含税)|应付金额', colSpan: '2|1', rowSpan: '1|1', field: 'yf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, visible: colSet.cur_excl_tax ? true : false},
+                    {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, visible: colSet.cur_excl_tax ? true : false},
+                    {title: '截止本期|付款金额', colSpan: '4|1', rowSpan: '1|1', field: 'end_pay_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, visible: colSet.end ? true : false},
+                    {title: '|扣款金额', colSpan: '|1', rowSpan: '|1', field: 'end_cut_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, visible: colSet.end ? true : false},
+                    {title: '|应付金额', colSpan: '|1', rowSpan: '|1', field: 'end_yf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, visible: colSet.end ? true : false},
+                    {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'end_sf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, visible: colSet.end ? true : false},
+                    {title: '截止本期(不含税)|应付金额', colSpan: '2|1', rowSpan: '1|1', field: 'end_yf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, visible: colSet.end_excl_tax ? true : false},
+                    {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'end_sf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, visible: colSet.end_excl_tax ? true : false},
+                    {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
+                    {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 120, formatter: '@', cellType: 'ellipsisAutoTip'},
                 ],
                 emptyRows: 0,
-                headRows: 1,
-                headRowHeight: [32],
+                headRows: 2,
+                headRowHeight: [25, 25],
                 defaultRowHeight: 21,
                 headerFont: '12px 微软雅黑',
                 font: '12px 微软雅黑',
@@ -82,6 +82,7 @@ $(document).ready(function() {
                         },
                     },
                 ],
+                tipsSum: true,
             };
             sjsSettingObj.setFxTreeStyle(this.spreadSetting, sjsSettingObj.FxTreeStyle.phasePay);
 
@@ -326,6 +327,7 @@ $(document).ready(function() {
                         }
                         self.refreshOperationValid();
                         billsTag.afterDeleteBills(refreshData.delete);
+                        costFile.deleteFileBySubNodeId(refreshData.delete.map(x => { return x.id; }));
                     });
                 });
             } else {
@@ -343,6 +345,7 @@ $(document).ready(function() {
                             sheet.setSelection(tree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
                             SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(refreshData.create[0])]);
                         }
+                        detailObj.loadDetailData(SpreadJsObj.getSelectObject(sheet));
                     }
                     self.refreshOperationValid();
                 });
@@ -479,7 +482,26 @@ $(document).ready(function() {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
-                    data[colSetting.field] = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    if (datas.length > 0) if (datas[0][colSetting.field] === undefined) continue;
+
+                    const newText = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    if (colSetting.type === 'Number') {
+                        const num = _.toNumber(newText);
+                        if (!_.isFinite(num)) {
+                            try {
+                                data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(newText));
+                            } catch (err) {
+                                toastr.error('粘贴的计算式非法');
+                                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                                return;
+                            }
+                        } else {
+                            data[colSetting.field] = num;
+                        }
+                    } else {
+                        data[colSetting.field] = newText;
+                    }
+
                     bPaste = true;
                 }
                 if (bPaste) {
@@ -518,8 +540,8 @@ $(document).ready(function() {
                     {title: '扣款金额', colSpan: '1', rowSpan: '1', field: 'cut_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
                     {title: '应付金额', colSpan: '1', rowSpan: '1', field: 'yf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
                     {title: '实付金额', colSpan: '1', rowSpan: '1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
-                    // {title: '应付金额(无税)', colSpan: '1', rowSpan: '1', field: 'yf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
-                    // {title: '实付金额(无税)', colSpan: '1', rowSpan: '1', field: 'sf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+                    {title: '应付金额(不含税)', colSpan: '1', rowSpan: '1', field: 'yf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+                    {title: '实付金额(不含税)', colSpan: '1', rowSpan: '1', field: 'sf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
                     {title: '本期批注', colSpan: '1', rowSpan: '1', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
                     {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 120, formatter: '@', cellType: 'ellipsisAutoTip'},
                 ],
@@ -564,6 +586,7 @@ $(document).ready(function() {
                         },
                     },
                 ],
+                tipsSum: true,
             };
             this.commonSpreadSetting = {
                 cols: [
@@ -574,8 +597,8 @@ $(document).ready(function() {
                     {title: '扣款金额', colSpan: '1', rowSpan: '1', field: 'cut_tp', hAlign: 2, width: 80, type: 'Number'},
                     {title: '应付金额', colSpan: '1', rowSpan: '1', field: 'yf_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
                     {title: '实付金额', colSpan: '1', rowSpan: '1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number'},
-                    // {title: '应付金额(无税)', colSpan: '1', rowSpan: '1', field: 'yf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
-                    // {title: '实付金额(无税)', colSpan: '1', rowSpan: '1', field: 'sf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+                    {title: '应付金额(不含税)', colSpan: '1', rowSpan: '1', field: 'yf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+                    {title: '实付金额(不含税)', colSpan: '1', rowSpan: '1', field: 'sf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
                     {title: '本期批注', colSpan: '1', rowSpan: '1', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
                     {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 120, formatter: '@', cellType: 'ellipsisAutoTip'},
                 ],
@@ -620,6 +643,7 @@ $(document).ready(function() {
                         },
                     },
                 ],
+                tipsSum: true,
             };
 
             this.initSpread();
@@ -670,7 +694,7 @@ $(document).ready(function() {
             }
             SpreadJsObj.initSheet(this.sheet, spreadSetting);
             this.reloadDetailData();
-            SpreadJsObj.locateRow(this.sheet, 0);
+            SpreadJsObj.locateRow(this.sheet, 0, false);
         }
         loadRelaData() {
             const select = SpreadJsObj.getSelectObject(this.sheet);
@@ -734,11 +758,16 @@ $(document).ready(function() {
                 if (col.type === 'Number') {
                     const num = _.toNumber(newText);
                     if (!_.isFinite(num)) {
-                        toastr.error('输入的数字非法');
-                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        return;
+                        try {
+                            updateData[col.field] = ZhCalc.mathCalcExpr(transExpr(newText));
+                        } catch (err) {
+                            toastr.error('输入的计算式非法');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                    } else {
+                        updateData[col.field] = num;
                     }
-                    updateData[col.field] = num;
                 } else {
                     updateData[col.field] = newText;
                 }
@@ -750,11 +779,16 @@ $(document).ready(function() {
                 if (col.type === 'Number') {
                     const num = _.toNumber(newText);
                     if (!_.isFinite(num)) {
-                        toastr.error('输入的数字非法');
-                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        return;
+                        try {
+                            addData[col.field] = ZhCalc.mathCalcExpr(transExpr(newText));
+                        } catch (err) {
+                            toastr.error('输入的计算式非法');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                    } else {
+                        addData[col.field] = num;
                     }
-                    addData[col.field] = num;
                 } else {
                     addData[col.field] = newText;
                 }
@@ -878,6 +912,7 @@ $(document).ready(function() {
         relaSpread: billsObj.spread,
         relaPosSpread: detailObj.spread,
         key: 'id',
+        keyType: 'uuid',
         treeId: 'tree_id',
         user_id: 'create_user_id',
         user_name: 'user_name',

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

@@ -190,8 +190,11 @@ $(document).ready(() => {
                     if (exist) {
                         toastr.warning('请勿输入重复的计算代号');
                     } else {
-                        select.calc_code = validText;
-                        select.field = 'num_' + select.calc_code.toLowerCase();
+                        const typeInfo = validColInfo.find(x => { return x.key === select.type; });
+                        if (typeInfo.type === 'num') {
+                            select.calc_code = validText;
+                            select.field = 'num_' + select.calc_code.toLowerCase();
+                        }
                     }
                 } else if (col.field === 'width') {
                     select.width = parseInt(validText);
@@ -471,7 +474,7 @@ $(document).ready(() => {
             return this.colSetData;
         }
         export() {
-            window.open(`template/ctd?tid=${this.template.id}`);
+            window.open(`/template/ctd?tid=${this.template.id}`);
         }
         import() {
             const self = this;

+ 4 - 2
app/public/js/gcl_gather.js

@@ -324,7 +324,8 @@ const gclGatherModel = (function () {
         if (!gclPart || gclPart.length === 0) return;
 
         for (const ag of gclPart) {
-            const condition = _.assign({ is_aux: ag.is_aux, name: ag.name, unit: ag.unit }, cacheLeafXmj);
+            const pos = gsPos.getPos(ag.pid);
+            const condition = _.assign({ is_aux: ag.is_aux, name: ag.name, unit: ag.unit, bwmx: pos.name }, cacheLeafXmj);
             let nag = _.find(gcl.ancGcl, condition);
             if (!nag) {
                 nag = condition;
@@ -703,6 +704,7 @@ const gclGatherModel = (function () {
     function _gatherAncillaryGcl(gcl, fields = ['jldy']) {
         fields.push('is_aux', 'name', 'unit');
         gcl.gatherAncGcl = [];
+        const pinyin = new PinYinOrder();
         for (const lx of gcl.ancGcl) {
             const condition = {};
             for (const f of fields) {
@@ -718,7 +720,7 @@ const gclGatherModel = (function () {
         }
         gcl.gatherAncGcl.sort((a, b) => {
             if (a.is_aux === b.is_aux) {
-                return a.name < b.name ? -1 : 1;
+                return pinyin.compareWord(a.name, b.name);
             } else {
                 return a.is_aux - b.is_aux;
             }

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

@@ -3886,7 +3886,7 @@ $(document).ready(function() {
         },
         clipboardPasting: function(e, info) {
             info.cancel = true;
-            const posNode = ancGclObj.posNode;
+            const posNode = SpreadJsObj.getSelectObject(posSheet);
             if (!posNode) {
                 toastr.error('数据错误,请选择计量单元后再试');
                 SpreadJsObj.reLoadRowData(info.sheet, info.row);

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

@@ -113,7 +113,7 @@ $(document).ready(() => {
             {title: '分项工程', colSpan: '1', rowSpan: '1', field: 'fxgc', hAlign: 0, width: 100, formatter: '@', visible: false},
             {title: '细目', colSpan: '1', rowSpan: '1', field: 'jldy', hAlign: 0, width: 100, formatter: '@', visible: false},
             {title: '计量单元', colSpan: '1', rowSpan: '1', field: 'bwmx', hAlign: 0, width: 100, formatter: '@'},
-            {title: '位置', colSpan: '1', rowSpan: '1', field: 'position', hAlign: 0, width: 100, formatter: '@'},
+            {title: '位置', colSpan: '1', rowSpan: '1', field: 'position', hAlign: 0, width: 100, formatter: '@', visible: false},
             {title: '台账数量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
         ],
         emptyRows: 0,
@@ -141,7 +141,7 @@ $(document).ready(() => {
             {title: '分部工程', colSpan: '1', rowSpan: '1', field: 'fbgc', hAlign: 0, width: 100, formatter: '@', visible: false},
             {title: '分项工程', colSpan: '1', rowSpan: '1', field: 'fxgc', hAlign: 0, width: 100, formatter: '@', visible: false},
             {title: '细目', colSpan: '1', rowSpan: '1', field: 'jldy', hAlign: 0, width: 100, formatter: '@'},
-            {title: '位置', colSpan: '1', rowSpan: '1', field: 'position', hAlign: 0, width: 100, formatter: '@'},
+            {title: '计量单元', colSpan: '1', rowSpan: '1', field: 'bwmx', hAlign: 0, width: 100, formatter: '@', visible: false},
             {title: '辅材', colSpan: '1', rowSpan: '1', field: 'is_aux', hAlign: 1, width: 60, formatter: '@', fixed: 1, cellType: 'checkbox'},
             {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 100, formatter: '@', fixed: 1},
             {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@', fixed: 1},

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

@@ -203,7 +203,7 @@ $(document).ready(function () {
 
         const btn = $(this);
         $.ajax({
-            url: '/profile/code',
+            url: '/profile/code?_csrf_j=' + csrf,
             type: 'post',
             data: { mobile: authMobile, type: 'shenpi' },
             dataTye: 'json',

+ 5 - 2
app/public/js/shares/cs_tools.js

@@ -1167,6 +1167,9 @@ const showSelectTab = function(select, spread, afterShow) {
             if (viewTags && viewTags.length > 0) viewTags.remove();
             billsTags.forEach(x => {x.display = false});
         };
+        const getTagId = function(obj) {
+            return setting.keyType == 'uuid' ? obj.attr('tag-id') : parseInt(obj.attr('tag-id'));
+        };
 
         const getTagEditHtml = function(tag) {
             const tagClass = classIndexes.find(x => {return x.color === tag.color}) || {};
@@ -1459,7 +1462,7 @@ const showSelectTab = function(select, spread, afterShow) {
         });
         $('body').on('click', '#tag-del', function () {
             const obj = $('[name=tag-edit]').parent();
-            postData(setting.updateUrl, {del: parseInt(obj.attr('tag-id'))}, function (result) {
+            postData(setting.updateUrl, {del: getTagId(obj)}, function (result) {
                 if (!result.del) return;
 
                 const bills = updateDatasAndShow(result);
@@ -1469,7 +1472,7 @@ const showSelectTab = function(select, spread, afterShow) {
         $('body').on('click', '#tag-edit-ok', function () {
             const obj = $('[name=tag-edit]').parent();
             const data = {
-                id: parseInt(obj.attr('tag-id')),
+                id: getTagId(obj),
                 share: $('#tag-share')[0].checked,
                 comment: $('#tag-comment').val(),
                 color: $('#tag-change-color').attr('tag-color'),

+ 2 - 2
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -1260,11 +1260,11 @@ const SpreadJsObj = {
         sheet.getParent().focus();
         sheet.showRow(index, spreadNS.VerticalPosition.center);
     },
-    locateRow: function (sheet, row) {
+    locateRow: function (sheet, row, focus = true) {
         const sels = sheet.getSelections();
         sheet.setSelection(row, sels[0].col, 1, 1);
         SpreadJsObj.reloadRowsBackColor(sheet, [row, sels[0].row]);
-        sheet.getParent().focus();
+        if (focus) sheet.getParent().focus();
         sheet.showRow(row, spreadNS.VerticalPosition.center);
     },
     locateDataBy: function(sheet, fun) {

+ 10 - 0
app/router.js

@@ -339,6 +339,9 @@ module.exports = app => {
     app.post('/sp/:id/contract/tender/:tid/col-set', sessionAuth, subProjectCheck, contractCheck, 'contractController.colSet');
     app.post('/sp/:id/contract/tender/:tid/detail/check', sessionAuth, subProjectCheck, contractCheck, 'contractController.checkAudit');
     app.post('/sp/:id/contract/tender/:tid/detail/:type/check', sessionAuth, subProjectCheck, contractCheck, 'contractController.checkAudit');
+    app.post('/sp/:id/contract/tender/:tid/detail/:type/:cid/supplement/:csid/file/upload', sessionAuth, subProjectCheck, contractCheck, 'contractController.uploadFile');
+    app.post('/sp/:id/contract/tender/:tid/detail/:type/:cid/supplement/:csid/file/delete', sessionAuth, subProjectCheck, contractCheck, 'contractController.deleteFile');
+    app.get('/sp/:id/contract/tender/:tid/detail/:type/:cid/supplement/:csid/file/:fid/download', sessionAuth, subProjectCheck, contractCheck, 'contractController.downloadFile');
     // 项目合同管理
     app.get('/sp/:id/contract', sessionAuth, subProjectCheck, contractCheck, 'contractController.detail');
     app.post('/sp/:id/contract/audit/save', sessionAuth, subProjectCheck, contractCheck, 'contractController.auditSave');
@@ -363,6 +366,9 @@ module.exports = app => {
     app.get('/sp/:id/contract/tender/setting', sessionAuth, subProjectCheck, 'contractController.setting');
     app.get('/sp/:id/contract/setting/shenpi', sessionAuth, subProjectCheck, 'contractController.shenpiSet');
     app.post('/sp/:id/contract/setting/update', sessionAuth, subProjectCheck, 'contractController.updateSetting');
+    app.post('/sp/:id/contract/detail/:type/:cid/supplement/:csid/file/upload', sessionAuth, subProjectCheck, contractCheck, 'contractController.uploadFile');
+    app.post('/sp/:id/contract/detail/:type/:cid/supplement/:csid/file/delete', sessionAuth, subProjectCheck, contractCheck, 'contractController.deleteFile');
+    app.get('/sp/:id/contract/detail/:type/:cid/supplement/:csid/file/:fid/download', sessionAuth, subProjectCheck, contractCheck, 'contractController.downloadFile');
 
 
     // 资料归集-列表
@@ -551,7 +557,9 @@ module.exports = app => {
     app.post('/sp/:id/cost/tender/:tid/addStage', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'costController.addStage');
     app.post('/sp/:id/cost/tender/:tid/delStage', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'costController.delStage');
     app.post('/sp/:id/cost/tender/:tid/:stype/auditors', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'costController.loadAuditors');
+    app.post('/sp/:id/cost/tender/:tid/colSet', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'costController.colSet');
     app.get('/sp/:id/cost/tender/:tid/:stype/:sorder/stage', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.stage');
+    app.get('/sp/:id/cost/tender/:tid/:stype/:sorder/compare', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.compare');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/load', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.stageLoad');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/update', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.stageUpdate');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/file/upload', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.uploadStageFile');
@@ -564,6 +572,8 @@ module.exports = app => {
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/audit/check', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.stageAuditCheck');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/audit/checkAgain', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.stageAuditCheckAgain');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/audit/checkCancel', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.stageAuditCheckCancel');
+    app.get('/sp/:id/cost/tender/:tid/gather', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'costController.gather');
+    app.post('/sp/:id/cost/tender/:tid/gather/load', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'costController.loadGatherData');
 
     // 安全管理
     // 安全计量

+ 2 - 2
app/service/calc_tmpl.js

@@ -80,7 +80,7 @@ const Cost = (function(){
         str: { title: '', colSpan: '1', rowSpan: '1', field: '', hAlign: 0, width: 80, formatter: '@' },
         num: { title: '', colSpan: '1', rowSpan: '1', field: '', hAlign: 2, width: 80, type: 'Number' },
         postil: { title: '', colSpan: '1', rowSpan: '1', field: '', hAlign: 0, width: 80, formatter: '@', cellType: 'ellipsisAutoTip'},
-        sf_percent: { title: '', colSpan: '1', rowSpan: '1', field: '', hAlign: 2, width: 80, type: 'Number' },
+        sf_percent: { title: '', colSpan: '1', rowSpan: '1', field: '', hAlign: 2, width: 80, type: 'Number', readOnly: true },
     };
     const ValidColInfo = [
         { key: 'code', name: '编号', fields: ['code'], valid: ['title', 'width'], def: { title: '编号', width: 80, type: 'code'} },
@@ -100,7 +100,7 @@ const Cost = (function(){
         { key: 'sf_percent', name: '实付比例', fields: ['sf_percent'], valid: ['title', 'width', 'calc_code'], def: { title: '实付比例', width: 80, type: 'sf_percent', decimal: 2} },
     ];
     ValidColInfo.forEach(vci => { return vci.count = vci.fields.length; });
-    return { EmptySpreadCache, BaseSpreadColSetting, ValidColInfo, ValidCount: 10, subSpreadReadOnly: ['code', 'name', 'party_b', 'settle_tp', 'in_excl_tax_tp'] };
+    return { EmptySpreadCache, BaseSpreadColSetting, ValidColInfo, ValidCount: 10, subSpreadReadOnly: ['code', 'name', 'party_b', 'yf_excl_tax_tp', 'sf_excl_tax_tp', 'in_excl_tax_tp'] };
 })();
 function randomWord(randomFlag, min, max){
     let str = "",

+ 159 - 0
app/service/contract_supplement.js

@@ -0,0 +1,159 @@
+'use strict';
+
+/**
+ * Created by EllisRan on 2020/3/3.
+ */
+
+const BaseService = require('../base/base_service');
+const contractConst = require('../const/contract');
+const auditConst = require('../const/audit').contract;
+const auditType = require('../const/audit').auditType;
+
+module.exports = app => {
+
+    class ContractSupplement extends BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'contract_supplement';
+            this.dataId = 'id';
+        }
+
+        async getSupplements(options, cid) {
+            const sql = 'SELECT * FROM ?? WHERE ' + this.ctx.helper._getOptionsSql(options) + ' AND `cid` = ? ORDER BY `create_time` DESC';
+            const sqlParams = [this.tableName, cid];
+            const list = await this.db.query(sql, sqlParams);
+            const shenpi_status = this.ctx.contract_tender ? this.ctx.subProject.page_show.openContractTenderShenpi : this.ctx.subProject.page_show.openContractSubProjectShenpi;
+            if (list.length > 0) {
+                const userList = await this.ctx.service.projectAccount.getAllDataByCondition({ where: { id: list.map(item => item.uid) } });
+                for (const l of list) {
+                    const userInfo = userList.find(item => item.id === l.uid);
+                    l.username = userInfo ? userInfo.name : '';
+                    l.files = await this.ctx.service.contractSupplementAtt.getAtt(l.id);
+                    if (shenpi_status) {
+                        await this.ctx.service.contractSpAudit.loadUser(l);
+                        await this.ctx.service.contractSpAudit.loadAuditViewData(l);
+                        await this.ctx.service.contractSpAudit.checkShenpi(l);
+                        if (l.status !== auditConst.status.checked || !l.final_auditor_str) {
+                            l.curAuditors2 = await this.ctx.service.contractSpAudit.getAuditorsByStatus(l.cid, l.id, l.status, l.times);
+                            if (l.status === auditConst.status.checked && !l.final_auditor_str) {
+                                const final_auditor_str = l.curAuditors2[0].audit_type === auditType.key.common
+                                    ? l.curAuditors2[0].name + (l.curAuditors2[0].role ? '-' + l.curAuditors2[0].role : '')
+                                    : this.ctx.helper.transFormToChinese(l.curAuditors2[0].audit_order) + '审';
+                                await this.defaultUpdate({ final_auditor_str, id: l.id });
+                                l.final_auditor_str = final_auditor_str;
+                            }
+                        }
+                    }
+                }
+            }
+            return list;
+        }
+
+        async add(options, cid, data) {
+            const node = await this.ctx.service.contract.getDataById(cid);
+            if (!node) {
+                throw '合同不存在';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                const insertData = {
+                    spid: options.spid || null,
+                    tid: options.tid || null,
+                    contract_type: options.contract_type,
+                    cid,
+                    uid: this.ctx.session.sessionUser.accountId,
+                    code: data.code,
+                    name: data.name,
+                    price: data.price || 0,
+                    party_a: data.party_a || 0,
+                    party_b: data.party_b || 0,
+                    sign_time: data.sign_time || null,
+                    address: data.address,
+                    content: data.content,
+                    create_time: new Date(),
+                    need_shenpi: options.spid ? this.ctx.subProject.page_show.openContractSubProjectShenpi : this.ctx.subProject.page_show.openContractTenderShenpi,
+                };
+                const result = await transaction.insert(this.tableName, insertData);
+                await this.calcContract(transaction, node);
+                await this.ctx.service.contractSpAudit.makeAudits(transaction, options, cid, result.insertId, this.ctx.session.sessionUser.accountId);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return { supplements: await this.getSupplements(options, cid), node: { update: node } };
+        }
+
+        async save(options, cid, data) {
+            if (!data.id) {
+                throw '参数有误';
+            }
+            const node = await this.ctx.service.contract.getDataById(cid);
+            if (!node) {
+                throw '合同不存在';
+            }
+            const csInfo = await this.getDataById(data.id);
+            if (!csInfo) {
+                throw '补充合同不存在';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, data);
+                await this.calcContract(transaction, node);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return { supplements: await this.getSupplements(options, cid), node: { update: node } };
+        }
+
+        async del(options, cid, csid) {
+            if (!csid) {
+                throw '参数有误';
+            }
+            const node = await this.ctx.service.contract.getDataById(cid);
+            if (!node) {
+                throw '合同不存在';
+            }
+            const csInfo = await this.getDataById(csid);
+            if (!csInfo) {
+                throw '补充合同不存在';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.delete(this.tableName, { id: csid });
+                // 删除合同附件
+                const attList = await this.ctx.service.contractSupplementAtt.getAllDataByCondition({ where: { csid } });
+                await this.ctx.helper.delFiles(attList);
+                await transaction.delete(this.ctx.service.contractSupplementAtt.tableName, { csid });
+                await transaction.delete(this.ctx.service.contractSpAudit.tableName, { csid });
+                await this.calcContract(transaction, node);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return { supplements: await this.getSupplements(options, cid), node: { update: node } };
+        }
+
+        async calcContract(transaction, node) {
+            const supplementsList = await transaction.query('SELECT `price` FROM ?? WHERE `cid` = ?', [this.tableName, node.id]);
+            let supplement_price = 0;
+            for (const l of supplementsList) {
+                supplement_price = this.ctx.helper.add(supplement_price, l.price);
+            }
+            node.supplement_price = supplement_price;
+            node.exist_supplement = supplementsList.length === 0 ? 0 : 1;
+            await transaction.update(this.ctx.service.contract.tableName, node);
+        }
+    }
+    return ContractSupplement;
+};

+ 61 - 0
app/service/contract_supplement_att.js

@@ -0,0 +1,61 @@
+'use strict';
+
+/**
+ * Created by EllisRan on 2020/3/3.
+ */
+
+const BaseService = require('../base/base_service');
+const contractConst = require('../const/contract');
+
+module.exports = app => {
+
+    class ContractSupplementAtt extends BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'contract_supplement_attachment';
+            this.dataId = 'id';
+        }
+
+        async getAtt(csid) {
+            const sql = 'SELECT a.*, b.name as username FROM ?? as a LEFT JOIN ?? as b ON a.`uid` = b.`id` WHERE a.`csid` = ? ORDER BY `upload_time` DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, csid];
+            const result = await this.db.query(sql, sqlParam);
+            return result.map(item => {
+                item.orginpath = this.ctx.app.config.fujianOssPath + item.filepath;
+                if (!this.ctx.helper.canPreview(item.fileext)) {
+                    item.filepath = `/sp/${this.ctx.subProject.id}/contract${item.tid ? '/tender/' + item.tid : ''}/detail/${contractConst.typeMap[item.contract_type]}/${item.cid}/supplement/${item.csid}/file/${item.id}/download`;
+                } else {
+                    item.filepath = this.ctx.app.config.fujianOssPath + item.filepath;
+                }
+                item.viewpath = this.ctx.helper.getPreviewPath(item.fileext, item.orginpath);
+                return item;
+            });
+        }
+
+        /**
+         * 存储上传的文件信息至数据库
+         * @param {Array} payload 载荷
+         * @return {Promise<void>} 数据库插入执行实例
+         */
+        async saveFileMsgToDb(payload) {
+            return await this.db.insert(this.tableName, payload);
+        }
+
+        /**
+         * 删除附件
+         * @param {Number} id - 附件id
+         * @return {void}
+         */
+        async delete(id) {
+            return await this.deleteById(id);
+        }
+    }
+    return ContractSupplementAtt;
+};

+ 10 - 5
app/service/cost_stage.js

@@ -25,9 +25,9 @@ module.exports = app => {
             super(ctx);
             this.tableName = 'cost_stage';
             this.stageType = {
-                ledger: { key: 'ledger', decimal: { tp: 6, tax: 2 }, shenpi_status: 'cost_stage_ledger', push_type: 'costStageLedger', dataService: 'costStageLedger', detailService: 'costStageDetail' },
-                book: { key: 'book', decimal: { tp: 6 }, shenpi_status: 'cost_stage_book', push_type: 'costStageBook', dataService: 'costStageBook', detailService: 'costStageBookDetail' },
-                analysis: { key: 'analysis', decimal: { tp: 6 }, shenpi_status: 'cost_stage_analysis', push_type: 'costStageAnalysis', dataService: 'costStageAnalysis', detailService: 'costStageAnalysisDetail' },
+                ledger: { key: 'ledger', name: '成本报审', decimal: { tp: 6, tax: 2 }, shenpi_status: 'cost_stage_ledger', push_type: 'costStageLedger', dataService: 'costStageLedger', detailService: 'costStageDetail' },
+                book: { key: 'book', name: '财务账面', decimal: { tp: 6 }, shenpi_status: 'cost_stage_book', push_type: 'costStageBook', dataService: 'costStageBook', detailService: 'costStageBookDetail' },
+                analysis: { key: 'analysis', name: '成本分析', decimal: { tp: 6 }, shenpi_status: 'cost_stage_analysis', push_type: 'costStageAnalysis', dataService: 'costStageAnalysis', detailService: 'costStageAnalysisDetail' },
             };
         }
 
@@ -279,8 +279,13 @@ module.exports = app => {
             } else {
                 // 会签,会签人部分审批通过时,只读,但是curSort需按原来的取值
                 stage.curSort = stage.flowAuditorIds.indexOf(accountId) >= 0 ? stage.curAuditors[0].active_order : stage.curAuditors[0].active_order - 1;
-                stage.readOnly = stage.curAuditorIds.indexOf(accountId) < 0;
-                stage.canCheck = stage.readOnly && stage.curAuditorIds.indexOf(accountId) > 0;
+                if (stage.curAuditors[0].audit_type === auditType.key.and) {
+                    stage.readOnly = !this.ctx.helper._.isEqual(stage.flowAuditorIds, stage.curAuditorIds);
+                    stage.canCheck = true;
+                } else {
+                    stage.readOnly = stage.curAuditorIds.indexOf(accountId) < 0;
+                    stage.canCheck = stage.readOnly && stage.curAuditorIds.indexOf(accountId) > 0;
+                }
             }
             await this.doCheckCanCancel(stage);
         }

+ 25 - 8
app/service/cost_stage_analysis.js

@@ -23,7 +23,7 @@ costFields.sumFields = [...costFields.calcFields, ...costFields.selfCalcFields];
 costFields.preCopyFields = [...costFields.treeFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.calcFields, ...costFields.selfCalcFields, ...costFields.taxFields];
 costFields.editQueryFields = [...costFields.baseFields, ...costFields.treeFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.calcFields, ...costFields.selfCalcFields, ...costFields.taxFields];
 costFields.readQueryFields = [...costFields.baseFields, ...costFields.treeFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.readFields, ...costFields.taxFields];
-costFields.compareQueryFields = [...costFields.baseFields, ...costFields.treeFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.taxFields, ...costFields.hisFields];
+costFields.compareQueryFields = [...costFields.baseFields, ...costFields.treeFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.sumFields, ...costFields.taxFields, ...costFields.hisFields];
 const calcType = { other: 0, in: 1, out: 2 };
 
 const auditConst = require('../const/audit').costStage;
@@ -52,6 +52,7 @@ module.exports = app => {
             });
             // this.depart = ctx.app.config.table_depart.light;
             this.tableName = 'cost_stage_analysis';
+            this.calcFields = costFields.sumFields;
         }
 
         // 继承方法
@@ -60,6 +61,16 @@ module.exports = app => {
                 data[f] = 0;
             }
         }
+        /**
+         * 删除相关数据 继承
+         * @param mid
+         * @param deleteData
+         * @return {Promise<void>}
+         * @private
+         */
+        async _deleteRelaData(mid, deleteData) {
+            await this.transaction.delete(this.ctx.service.costStageFile.tableName, { rela_id: this._.map(deleteData, 'id') });
+        }
 
         _getDefaultData(data, stage) {
             data.id = this.uuid.v4();
@@ -102,18 +113,18 @@ module.exports = app => {
         }
 
         async getCostStageData(costStage) {
-            if (costStage.rela_stage.sid) costStage.relaStage = await this.ctx.service.costStage.getStage(costStage.rela_stage.sid);
+            if (costStage.stage_type === 'book') costStage.relaStage = await this.ctx.service.costStage.getStage(costStage.rela_stage.sid);
             const ledgerData = costStage.relaStage
                 ? await this.ctx.service.costStageLedger.getReadData(costStage.relaStage)
                 : await this.ctx.service.costStageLedger.getReadData(costStage);
-            if (costStage.relaStage) {
+            if (costStage.stage_type === 'book') {
                 const bookData = await this.ctx.service.costStageBook.getReadData(costStage);
                 this.ctx.helper.assignRelaData(ledgerData, [
                     { data: bookData, fields: ['end_in_tp', 'end_in_excl_tax_tp'], prefix: '', relaId: 'ledger_id' },
                 ]);
             }
-            const detail = costStage.relaStage
-                ? await this.ctx.service.costStageBookDetail.getEndData(costStage.relaStage)
+            const detail = costStage.stage_type === 'book'
+                ? await this.ctx.service.costStageBookDetail.getEndData(costStage)
                 : await this.ctx.service.costStageDetail.getEndData(costStage);
             const ledger = new Ledger.baseTree(this.ctx, {
                 id: 'tree_id', pid: 'tree_pid', order: 'tree_order',
@@ -184,6 +195,7 @@ module.exports = app => {
                             d_order: i+1,
                             code: d.code, name: d.name, party_b: d.party_b || '', tax: d.tax || 0,
                             yf_excl_tax_tp: d.yf_excl_tax_tp || 0, in_excl_tax_tp: d.in_excl_tax_tp || 0, sf_excl_tax_tp: d.sf_excl_tax_tp || 0,
+                            is_deal: d.is_deal,
                         };
                         this.ctx.service.calcTmpl.calcByTemplate(idd, idd, idd, calcTemplate.calc_expr);
                         idd.sf_percent = idd.yf_excl_tax_tp ? this.ctx.helper.mul(this.ctx.helper.div(idd.sf_excl_tax_tp, idd.yf_excl_tax_tp), 100, 2) : 0;
@@ -275,8 +287,10 @@ module.exports = app => {
                     for (const prop of costFields.selfCalcFields) {
                         ln[prop] = preSource ? preSource[prop] : 0;
                     }
-                    this.ctx.service.calcTmpl.calcByTemplate(ln, ln, ln, stage.calcTemplate.calc_expr);
                 } else {
+                    for (const prop of costFields.sumFields) {
+                        ln[prop] = 0;
+                    }
                     for (const [i, d] of node.detail.entries()) {
                         const ld = {
                             id: this.uuid.v4(), tender_id: stage.tid, stage_id: stage.id,
@@ -295,12 +309,15 @@ module.exports = app => {
                             ld[prop] = preDetailSource ? preDetailSource[prop] || 0 : 0;
                         }
                         this.ctx.service.calcTmpl.calcByTemplate(ld, ld, ld, stage.calcTemplate.calc_expr);
-                        for (const prop of costFields.selfCalcFields) {
+                        ld.sf_percent = ld.yf_excl_tax_tp ? this.ctx.helper.mul(this.ctx.helper.div(ld.sf_excl_tax_tp, ld.yf_excl_tax_tp), 100, 2) : 0;
+                        for (const prop of costFields.sumFields) {
                             ln[prop] = this.ctx.helper.add(ln[prop], ld[prop]);
                         }
                         insertDetailData.push(ld);
                     }
                 }
+                this.ctx.service.calcTmpl.calcByTemplate(ln, ln, ln, stage.calcTemplate.calc_expr);
+                ln.sf_percent = ln.yf_excl_tax_tp ? this.ctx.helper.mul(this.ctx.helper.div(ln.sf_excl_tax_tp, ln.yf_excl_tax_tp), 100, 2) : 0;
             }
 
             await transaction.insert(this.tableName, insertData);
@@ -637,7 +654,7 @@ module.exports = app => {
                     if (fi >= 0) his.splice(fi, 1);
                     const newHis = { ...auditInfo };
                     for (const f of costFields.sumFields) {
-                        newHis[f] = data[f];
+                        newHis[f] = l[f];
                     }
                     his.push(newHis);
                     data.calc_his = JSON.stringify(his);

+ 5 - 5
app/service/cost_stage_analysis_detail.js

@@ -16,13 +16,13 @@ const costFields = {
     readFields: ['calc_read'],
     hisFields: ['calc_his'],
     taxFields: ['tax'],
-    baseFields: ['id', 'cost_id', 'tender_id', 'stage_id', 'ledger_id', 'source_id', 'is_deal'],
+    baseFields: ['id', 'cost_id', 'tender_id', 'stage_id', 'ledger_id', 'source_id', 'is_deal', 'd_order'],
 };
 costFields.sumFields = [...costFields.calcFields, ...costFields.selfCalcFields];
 costFields.preCopyFields = [...costFields.selfTextFields, ...costFields.calcFields, ...costFields.selfCalcFields, ...costFields.taxFields];
-costFields.editQueryFields = [...costFields.baseFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.calcFields, ...costFields.selfCalcFields, ...costFields.taxFields];
+costFields.editQueryFields = [...costFields.baseFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.sumFields, ...costFields.taxFields];
 costFields.readQueryFields = [...costFields.baseFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.readFields, ...costFields.taxFields];
-costFields.compareQueryFields = [...costFields.baseFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.taxFields, ...costFields.hisFields];
+costFields.compareQueryFields = [...costFields.baseFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.sumFields, ...costFields.taxFields, ...costFields.hisFields];
 
 module.exports = app => {
     class CostStageDetail extends app.BaseService {
@@ -46,7 +46,7 @@ module.exports = app => {
             });
             result.forEach(x => {
                 x.calc_read = x.calc_read ? JSON.parse(x.calc_read) : {};
-                for(const prop of costFields.calcFields) {
+                for(const prop of costFields.sumFields) {
                     x[prop] = x.calc_read[prop] || 0;
                 }
             });
@@ -147,7 +147,7 @@ module.exports = app => {
                     if (fi >= 0) his.splice(fi, 1);
                     const newHis = { ...auditInfo };
                     for (const f of costFields.sumFields) {
-                        newHis[f] = data[f];
+                        newHis[f] = l[f];
                     }
                     his.push(newHis);
                     data.calc_his = JSON.stringify(his);

+ 6 - 1
app/service/cost_stage_audit.js

@@ -213,7 +213,7 @@ module.exports = app => {
                     result.push(x);
                 }
             });
-            return result;
+            return latestGroup;
         }
         // ***** 查询审批人相关
 
@@ -471,6 +471,11 @@ module.exports = app => {
             await this.ctx.service[stage.typeInfo.dataService].auditCachePre(transaction, stage.id, {
                 user_id: audit.audit_id, audit_times: audit.audit_times, audit_order: audit.audit_order, active_order: audit.active_order,
             });
+            if (stage.typeInfo.detailService) {
+                await this.ctx.service[stage.typeInfo.detailService].auditCachePre(transaction, stage.id, {
+                    user_id: audit.audit_id, audit_times: audit.audit_times, audit_order: audit.audit_order, active_order: audit.active_order,
+                });
+            }
         }
         async start(stage) {
             const audits = await this.getAllDataByCondition({ where: { stage_id: stage.id, audit_times: stage.audit_times, audit_order: 1 } });

+ 12 - 1
app/service/cost_stage_book.js

@@ -26,6 +26,7 @@ module.exports = app => {
         constructor(ctx) {
             super(ctx);
             this.tableName = 'cost_stage_book';
+            this.calcFields = costFields.calcFields;
         }
 
         async getEditData(stage) {
@@ -75,11 +76,20 @@ module.exports = app => {
                 where: { stage_id: preStage.id },
                 columns: costFields.editQueryFields
             });
+            if (!stage.rela_stage) throw '财务账面关联的成本报审数据错误';
+            const relaStage = JSON.parse(stage.rela_stage);
+            const curLedger = await this.ctx.service.costStageLedger.getAllDataByCondition({
+                where: { stage_id: relaStage.sid },
+                columns: ['id', 'cost_id'],
+            });
             const insertData = [];
             for (const b of preBooks) {
                 b.id = this.uuid.v4();
                 b.stage_id = stage.id;
                 delete b.postil;
+                const cl = curLedger.find(x => { return x.cost_id === b.cost_id });
+                if (!cl) throw '财务账面关联的成本报审节点错误';
+                b.ledger_id = cl.id;
                 for(const prop of costFields.curFields) {
                     b['pre_' + prop] = this.ctx.helper.add(b['pre_' + prop], b[prop]);
                     b[prop] = 0;
@@ -121,6 +131,7 @@ module.exports = app => {
                     const divNum = this.ctx.helper.add(1, this.ctx.helper.div(cl.tax, 100));
                     nd.in_excl_tax_tp = this.ctx.helper.div(nd.in_tp, divNum, this.ctx.costStage.decimal.tp);
                 }
+                if (d.in_excl_tax_tp !== undefined) nd.in_excl_tax_tp = this.ctx.helper.round(d.in_excl_tax_tp, this.ctx.costStage.decimal.tp);
                 if (od) {
                     updateDetail.push(nd);
                 } else {
@@ -172,7 +183,7 @@ module.exports = app => {
                     if (fi >= 0) his.splice(fi, 1);
                     const newHis = { ...auditInfo };
                     for (const f of costFields.curFields) {
-                        newHis[f] = data[f];
+                        newHis[f] = l[f];
                     }
                     his.push(newHis);
                     data.calc_his = JSON.stringify(his);

+ 18 - 45
app/service/cost_stage_book_detail.js

@@ -57,52 +57,21 @@ module.exports = app => {
             return result;
         }
         async getEndData(stage) {
-            const commonDetailSql =
-                'SELECT sd.cost_id, sd.code, sd.name, sd.tax, sd.is_deal, sd.id AS source_cid, sd.pay_tp, sd.cut_tp, sd.yf_tp, sd.sf_tp, sd.yf_excl_tax_tp, sd.sf_excl_tax_tp, sbd.in_tp, sbd.in_excl_tax_tp ' +
-                `  FROM ${this.ctx.service.costStageDetail.tableName} sd LEFT JOIN ${this.tableName} sbd ON sbd.detail_id = sd.id` +
-                `    LEFT JOIN ${this.ctx.service.costStage.tableName} cs ON sd.stage_id = cs.id` +
-                '  WHERE cs.tid = ? AND cs.stage_order <= ? AND sd.is_deal = 0';
-            const commonDetail = await this.db.query(commonDetailSql, [stage.tid, stage.stage_order]);
-            // 以下sql需要8.0的mysql才支持
-            // const dealDetailSql =
-            //     'SELECT * FROM ( ' +
-            //     '  SELECT sd.cost_id, sd.code, sd.name, sd.tax, sd.source_cid AS source_id,' +
-            //     '      sum(sd.pay_tp) OVER (PARTITION BY sd.source_cid) AS pay_tp,' +
-            //     '      sum(sd.cut_tp) OVER (PARTITION BY sd.source_cid) AS cut_tp,' +
-            //     '      sum(sd.yf_tp) OVER (PARTITION BY sd.source_cid) AS yf_tp,' +
-            //     '      sum(sd.sf_tp) OVER (PARTITION BY sd.source_cid) AS sf_tp,' +
-            //     '      sum(sd.yf_excl_tax_tp) OVER (PARTITION BY sd.source_cid) AS yf_excl_tax_tp,' +
-            //     '      sum(sd.sf_excl_tax_tp) OVER (PARTITION BY sd.source_cid) AS sf_excl_tax_tp,' +
-            //     '      sum(sbd.in_tp) OVER (PARTITION BY sd.source_cid) AS in_tp,' +
-            //     '      sum(sbd.in_excl_tax_tp) OVER (PARTITION BY sd.source_cid) AS in_excl_tax_tp,' +
-            //     '      ROW_NUMBER() OVER (PATITION BY sd.source_cid ORDER BY create_time DESC) AS rn' +
-            //     `    FROM ${this.ctx.service.costStageDetail.tableName} sd LEFT JOIN ${this.tableName} sbd ON sbd.detail_id = sd.id` +
-            //     `      LEFT JOIN ${this.ctx.service.costStage.tableName} cs ON sd.stage_id = cs.id` +
-            //     '    WHERE cs.tid = ? AND cs.stage_order <= ? AND sd.is_deal = 1' +
-            //     '  ) t WHERE t.rn = 1';
-            const dealDetailSql =
-                'SELECT a.code, a.name, a.tax, a.is_deaL, b.* FROM (' +
-                '  SELECT sd.cost_id, sd.code, sd.name, sd.tax, sd.is_deal, sd.source_cid' +
-                '    FROM (' +
-                '      SELECT cost_id, source_cid, max(add_time) AS latest_time FROM zh_cost_stage_detail WHERE tender_id = ? AND stage_order <= ? AND is_deal = 1 GROUP BY cost_id, source_cid' +
-                '    ) lastTable LEFT JOIN zh_cost_stage_detail sd ON lastTable.latest_time = sd.add_time AND sd.cost_id = lastTable.cost_id AND sd.source_cid = lastTable.source_cid' +
-                '  ) a' +
-                '  LEFT JOIN ( ' +
-                '    SELECT csd.cost_id, csd.source_cid, sum(csd.pay_tp) AS pay_tp, sum(csd.cut_tp) AS cut_tp, ' +
-                '        sum(csd.yf_tp) AS yf_tp, sum(csd.sf_tp) AS sf_tp, ' +
-                '        sum(csd.yf_excl_tax_tp) AS yf_excl_tax_tp, sum(csd.sf_excl_tax_tp) AS sf_excl_tax_tp,' +
-                '        sum(csbd.in_tp) AS in_tp, sum(csbd.in_excl_tax_tp) As in_excl_tax_tp' +
-                '      FROM zh_cost_stage_detail csd LEFT JOIN zh_cost_stage_book_detail csbd ON csd.id = csbd.detail_id' +
-                '      WHERE csd.tender_id = ? AND csd.stage_order <= ? AND csd.is_deal = 1 GROUP BY csd.cost_id, csd.source_cid' +
-                '  ) b ON a.cost_id = b.cost_id AND a.source_cid = b.source_cid';
-            const dealDetail = await this.db.query(dealDetailSql, [stage.tid, stage.stage_order, stage.tid, stage.stage_order]);
-            return [...commonDetail, ...dealDetail];
+            if (!stage) return [];
+            if (stage.stage_type !== 'book') throw '关联财务账面错误';
+
+            const allStages = await this.db.query(`SELECT rela_stage FROM ${this.ctx.service.costStage.tableName} WHERE tid = ? AND stage_type = 'book' AND stage_order <= ?`, [stage.tid, stage.stage_order]);
+            const relaStageIds = allStages.map(x => {
+                const rs = JSON.parse(x.rela_stage);
+                return rs.sid;
+            });
+            return await this.ctx.service.costStageDetail.getEndDataByStageId(stage.tid, relaStageIds);
         }
 
         async _getBookUpdateData(data, ledger_id, details) {
             const detailDatas = details || await this.getAllDataByCondition({ columns: ['id', 'ledger_id', ...costFields.curFields], where: { ledger_id, stage_id: this.ctx.costStage.id } });
 
-            const updateData = { id: ledger_id, update_user_id: this.ctx.session.sessionUser.accountId };
+            const updateData = { update_user_id: this.ctx.session.sessionUser.accountId };
             for (const prop of costFields.curFields) {
                 updateData[prop] = 0;
             }
@@ -142,7 +111,8 @@ module.exports = app => {
 
                 const nd = od
                     ? { id: od.id, update_user_id: user_id }
-                    : { id: this.uuid.v4(), tender_id: this.ctx.costStage.tid, stage_id: this.ctx.costStage.id, ledger_id: d.ledger_id, detail_id: d.detail_id, add_user_id: user_id, update_user_id: user_id };
+                    : { id: this.uuid.v4(), tender_id: this.ctx.costStage.tid, stage_id: this.ctx.costStage.id, stage_order: this.ctx.costStage.stage_order,
+                        ledger_id: d.ledger_id, detail_id: d.detail_id, add_user_id: user_id, update_user_id: user_id };
                 for (const prop of costFields.textFields) {
                     if (d[prop] !== undefined) nd[prop] = d[prop];
                 }
@@ -152,6 +122,7 @@ module.exports = app => {
                     const divNum = this.ctx.helper.add(1, this.ctx.helper.div(cd.tax, 100));
                     nd.in_excl_tax_tp = this.ctx.helper.div(nd.in_tp, divNum, this.ctx.costStage.decimal.tp);
                 }
+                if (d.in_excl_tax_tp !== undefined) nd.in_excl_tax_tp = this.ctx.helper.round(d.in_excl_tax_tp, this.ctx.costStage.decimal.tp);
                 if (od) {
                     updateDetail.push(nd);
                 } else {
@@ -159,15 +130,18 @@ module.exports = app => {
                 }
             }
             const bookUpdate = calc ? await this._getBookUpdateData([...insertDetail, ...updateDetail], orgDetail[0].ledger_id) : null;
-            const orgBook = await this.ctx.service.costStageBook.getDataByCondition({ ledger_id: orgDetail[0].ledger_id });
+            const orgBook = await this.ctx.service.costStageBook.getDataByCondition({ stage_id: this.ctx.costStage.id, ledger_id: orgDetail[0].ledger_id });
             const orgLedger = await this.ctx.service.costStageLedger.getDataById(orgDetail[0].ledger_id);
             if (bookUpdate) {
                 if (!orgBook) {
+                    bookUpdate.id = this.uuid.v4();
                     bookUpdate.tender_id = this.ctx.costStage.tid;
                     bookUpdate.stage_id = this.ctx.costStage.id;
                     bookUpdate.ledger_id = orgLedger.id;
                     bookUpdate.cost_id = orgLedger.cost_id;
                     bookUpdate.add_user_id = user_id;
+                } else {
+                    bookUpdate.id = orgBook.id;
                 }
             }
 
@@ -207,7 +181,6 @@ module.exports = app => {
             return { detail: { update: [...insertDetail, ...updateDetail] }, ledger: bookUpdate };
         }
 
-
         async auditCache(transaction, stageId, auditInfo) {
             const leaf = await this.getAllDataByCondition({ where: { stage_id: stageId } });
             const updateData = [];
@@ -227,7 +200,7 @@ module.exports = app => {
                     if (fi >= 0) his.splice(fi, 1);
                     const newHis = { ...auditInfo };
                     for (const f of costFields.curFields) {
-                        newHis[f] = data[f];
+                        newHis[f] = l[f];
                     }
                     his.push(newHis);
                     data.calc_his = JSON.stringify(his);

+ 50 - 23
app/service/cost_stage_detail.js

@@ -57,41 +57,62 @@ module.exports = app => {
             });
             return result;
         }
-        async getEndData(stage) {
-            const commonDetailSql = 'SELECT sd.cost_id, sd.id AS source_id, sd.code, sd.name, sd.tax, sd.pay_tp, sd.cut_tp, sd.yf_tp, sd.sf_tp, sd.yf_excl_tax_tp, sd.sf_excl_tax_tp ' +
-                `  FROM ${this.tableName} sd LEFT JOIN ${this.ctx.service.costStage.tableName} cs ON sd.stage_id = cs.id` +
-                '  WHERE cs.tid = ? AND cs.stage_order <= ? AND sd.is_deal = 0';
-            const commonDetail = await this.db.query(commonDetailSql, [stage.tid, stage.stage_order]);
-            // 以下sql只有在mysqlV8.0以后才可使用
+        async getEndDataByStageId(tenderId, stageIds) {
+            if (!stageIds || stageIds.length === 0) return [];
+
+            const idSql = stageIds.map(x => { return `'${x}'`; }).join(', ');
+            const commonDetailSql =
+                'SELECT sd.cost_id, sd.code, sd.name, sd.tax, sd.is_deal, sd.id AS source_cid, sd.pay_tp, sd.cut_tp, sd.yf_tp, sd.sf_tp, sd.yf_excl_tax_tp, sd.sf_excl_tax_tp, sbd.in_tp, sbd.in_excl_tax_tp ' +
+                `  FROM ${this.ctx.service.costStageDetail.tableName} sd LEFT JOIN ${this.ctx.service.costStageBookDetail.tableName} sbd ON sbd.detail_id = sd.id` +
+                `  WHERE sd.tender_id = ? AND sd.stage_id in (${idSql}) AND sd.is_deal = 0 ORDER BY sd.code ASC`;
+            const commonDetail = await this.db.query(commonDetailSql, [tenderId]);
+            // 以下sql需要8.0的mysql才支持,暂不使用
             // const dealDetailSql =
             //     'SELECT * FROM ( ' +
-            //     '  SELECT sd.cost_id, sd.code, sd.name, sd.tax, source_cid AS source_id,' +
+            //     '  SELECT sd.cost_id, sd.code, sd.name, sd.tax, sd.source_cid AS source_id,' +
             //     '      sum(sd.pay_tp) OVER (PARTITION BY sd.source_cid) AS pay_tp,' +
             //     '      sum(sd.cut_tp) OVER (PARTITION BY sd.source_cid) AS cut_tp,' +
             //     '      sum(sd.yf_tp) OVER (PARTITION BY sd.source_cid) AS yf_tp,' +
             //     '      sum(sd.sf_tp) OVER (PARTITION BY sd.source_cid) AS sf_tp,' +
             //     '      sum(sd.yf_excl_tax_tp) OVER (PARTITION BY sd.source_cid) AS yf_excl_tax_tp,' +
             //     '      sum(sd.sf_excl_tax_tp) OVER (PARTITION BY sd.source_cid) AS sf_excl_tax_tp,' +
+            //     '      sum(sbd.in_tp) OVER (PARTITION BY sd.source_cid) AS in_tp,' +
+            //     '      sum(sbd.in_excl_tax_tp) OVER (PARTITION BY sd.source_cid) AS in_excl_tax_tp,' +
             //     '      ROW_NUMBER() OVER (PATITION BY sd.source_cid ORDER BY create_time DESC) AS rn' +
-            //     `    FROM ${this.tableName} sd LEFT JOIN ${this.ctx.service.costStage.tableName} cs ON sd.stage_id = cs.id` +
+            //     `    FROM ${this.ctx.service.costStageDetail.tableName} sd LEFT JOIN ${this.tableName} sbd ON sbd.detail_id = sd.id` +
+            //     `      LEFT JOIN ${this.ctx.service.costStage.tableName} cs ON sd.stage_id = cs.id` +
             //     '    WHERE cs.tid = ? AND cs.stage_order <= ? AND sd.is_deal = 1' +
             //     '  ) t WHERE t.rn = 1';
             const dealDetailSql =
-                'SELECT a.code, a.name, a.tax, b.* FROM (' +
-                '  SELECT sd.cost_id, sd.code, sd.name, sd.tax, sd.source_cid' +
+                'SELECT a.code, a.name, a.tax, a.is_deaL, a.party_b, b.* FROM (' +
+                '  SELECT sd.cost_id, sd.code, sd.name, sd.party_b, sd.tax, sd.is_deal, sd.source_cid' +
                 '    FROM (' +
-                '      SELECT source_cid, max(add_time) AS latest_time FROM zh_cost_stage_detail WHERE tender_id = ? AND stage_order <= ? AND is_deal = 1 GROUP BY cost_id, source_cid' +
-                '    ) lastTable LEFT JOIN zh_cost_stage_detail sd ON lastTable.latest_time = sd.add_time AND sd.cost_id = lastTable.cost_id AND sd.source_cid = lastTable.source_cid' +
+                `      SELECT cost_id, source_cid, max(add_time) AS latest_time FROM ${this.ctx.service.costStageDetail.tableName} ` +
+                `        WHERE tender_id = ? AND stage_id in(${idSql}) AND is_deal = 1 GROUP BY cost_id, source_cid` +
+                `    ) lastTable LEFT JOIN ${this.ctx.service.costStageDetail.tableName} sd ON lastTable.latest_time = sd.add_time AND sd.cost_id = lastTable.cost_id AND sd.source_cid = lastTable.source_cid` +
                 '  ) a' +
                 '  LEFT JOIN ( ' +
-                '    SELECT cost_id, source_cid, sum(pay_tp) AS pay_tp, sum(cut_tp) AS cut_tp, ' +
-                '        sum(yf_tp) AS yf_tp, sum(sf_tp) AS sf_tp, ' +
-                '        sum(yf_excl_tax_tp) AS yf_excl_tax_tp, sum(sf_excl_tax_tp) AS sf_excl_tax_tp' +
-                '      FROM zh_cost_stage_detail WHERE tender_id = ? AND stage_order <= ? AND is_deal = 1 GROUP BY cost_id, source_cid' +
-                ') b ON a.cost_id = b.cost_id AND a.source_cid = b.source_cid';
-            const dealDetail = await this.db.query(dealDetailSql, [stage.tid, stage.stage_order, stage.tid, stage.stage_order]);
+                '    SELECT csd.cost_id, csd.source_cid, sum(csd.pay_tp) AS pay_tp, sum(csd.cut_tp) AS cut_tp, ' +
+                '        sum(csd.yf_tp) AS yf_tp, sum(csd.sf_tp) AS sf_tp, ' +
+                '        sum(csd.yf_excl_tax_tp) AS yf_excl_tax_tp, sum(csd.sf_excl_tax_tp) AS sf_excl_tax_tp,' +
+                '        sum(csbd.in_tp) AS in_tp, sum(csbd.in_excl_tax_tp) As in_excl_tax_tp' +
+                `      FROM ${this.ctx.service.costStageDetail.tableName} csd LEFT JOIN ${this.ctx.service.costStageBookDetail.tableName} csbd ON csd.id = csbd.detail_id` +
+                `      WHERE csd.tender_id = ? AND csd.stage_id in (${idSql}) AND csd.is_deal = 1 GROUP BY csd.cost_id, csd.source_cid` +
+                '  ) b ON a.cost_id = b.cost_id AND a.source_cid = b.source_cid' +
+                '  ORDER BY a.code ASC';
+            const dealDetail = await this.db.query(dealDetailSql, [tenderId, tenderId]);
             return [...commonDetail, ...dealDetail];
         }
+        async getEndData(stage) {
+            if (!stage) return [];
+            if (stage.stage_type !== 'ledger') throw '关联成本报审错误';
+
+            const allStages = await this.db.query(`SELECT id FROM ${this.ctx.service.costStage.tableName} WHERE tid = ? AND stage_type = 'ledger' AND stage_order <= ?`, [stage.tid, stage.stage_order]);
+            const relaStageIds = allStages.map(x => {
+                return x.id;
+            });
+            return await this.getEndDataByStageId(stage.tid, relaStageIds);
+        }
 
         async _getLedgerUpdateData(data, ledger_id, details) {
             const detailDatas = details || await this.getAllDataByCondition({ columns: ['id', 'ledger_id', ...costFields.curFields], where: { ledger_id, stage_id: this.ctx.costStage.id } });
@@ -186,7 +207,7 @@ module.exports = app => {
                 await conn.delete(this.tableName, { id: data });
                 if (updateData.length > 0) await conn.updateRows(this.tableName, updateData);
                 await conn.delete(this.ctx.service.costStageTag.tableName, { rela_sub_id: data });
-                await conn.delete(this.ctx.service.costStageFile.tableName, { rela_sub_id: data });
+                await conn.update(this.ctx.service.costStageFile.tableName, { is_deleted: 1 }, { where: { rela_sub_id: data } });
                 await conn.update(this.ctx.service.costStageLedger.tableName, billsUpdate);
                 await conn.commit();
                 return [data, updateData, billsUpdate];
@@ -283,8 +304,9 @@ module.exports = app => {
 
             const usedFilter = !used || used === '全部' ? '' : this.db.format(` AND cp.used = ?`, [used]);
             const paySql = `SELECT cp.*, c.c_code, c.name, c.party_b, c.tax FROM ${this.ctx.service.contractPay.tableName} cp LEFT JOIN ${this.ctx.service.contract.tableName} c ON cp.cid = c.id ` +
-                           `    WHERE cp.cid IN(${ids.map(x => { return `'${x}'`}).join(', ')}) AND cp.pay_time >= ? AND cp.pay_time <= ?` + usedFilter;
-            const validPays = await this.db.query(paySql, [beginTime, endTime]);
+                           `    WHERE cp.cid IN(${ids.map(x => { return `'${x}'`}).join(', ')}) AND cp.pay_time >= ? AND cp.pay_time <= ?` + usedFilter +
+                           '    ORDER BY c.c_code ASC';
+            const validPays = (ids && ids.length > 0) ? await this.db.query(paySql, [beginTime, endTime]) : [];
 
             const insertDetails = [];
             for (const pay of validPays) {
@@ -311,9 +333,14 @@ module.exports = app => {
             const detailDatas = await this.getAllDataByCondition({ columns: ['id'], where: { ledger_id: ledgerId, stage_id: this.ctx.costStage.id } });
             const billsUpdate = await this._getLedgerUpdateData(insertDetails, ledgerId, []);
             billsUpdate.is_deal = 1;
+            billsUpdate.tax = 0;
             const conn = await this.db.beginTransaction();
             try {
                 await conn.delete(this.tableName, { stage_id: this.ctx.costStage.id, ledger_id: ledgerId });
+                if (detailDatas.length > 0) {
+                    await this.transaction.update(this.ctx.service.costStageFile.tableName, { is_deleted: 1 }, { where: { rela_sub_id: detailDatas.map(x => { return x.id; }) } });
+                    await conn.delete(this.ctx.service.costStageTag.tableName, { rela_sub_id: detailDatas.map(x => { return x.id; }) });
+                }
                 if (insertDetails.length > 0) await conn.insert(this.tableName, insertDetails);
                 await conn.update(this.ctx.service.costStageLedger.tableName, billsUpdate);
                 await conn.commit();
@@ -347,7 +374,7 @@ module.exports = app => {
                 const diff = costFields.curFields.find(x => {
                     return l[x] !== l['read_' + x];
                 });
-                if (diff) {
+                if (diff || !l.calc_his) {
                     const data = { id: l.id };
                     // cache read
                     for (const f of costFields.curFields) {
@@ -359,7 +386,7 @@ module.exports = app => {
                     if (fi >= 0) his.splice(fi, 1);
                     const newHis = { ...auditInfo };
                     for (const f of costFields.curFields) {
-                        newHis[f] = data[f];
+                        newHis[f] = l[f];
                     }
                     his.push(newHis);
                     data.calc_his = JSON.stringify(his);

+ 15 - 2
app/service/cost_stage_ledger.js

@@ -48,6 +48,7 @@ module.exports = app => {
             });
             // this.depart = ctx.app.config.table_depart.light;
             this.tableName = 'cost_stage_ledger';
+            this.calcFields = costFields.calcFields;
         }
 
         // 继承方法
@@ -161,6 +162,18 @@ module.exports = app => {
         }
 
         /**
+         * 删除相关数据 继承
+         * @param mid
+         * @param deleteData
+         * @return {Promise<void>}
+         * @private
+         */
+        async _deleteRelaData(mid, deleteData) {
+            await this.ctx.service.costStageDetail.deletePartData(this.transaction, mid, this._.map(deleteData, 'id'));
+            await this.transaction.update(this.ctx.service.costStageFile.tableName, { is_deleted: 1 }, { where: { rela_id: this._.map(deleteData, 'id') } });
+            await this.transaction.delete(this.ctx.service.costStageTag.tableName, { rela_id: this._.map(deleteData, 'id') });
+        }
+        /**
          * 新增数据(供内部或其他service类调用, controller不可直接使用)
          * @param {Array|Object} data - 新增数据
          * @param {Number} stageId - 期id
@@ -468,7 +481,7 @@ module.exports = app => {
                 const diff = costFields.curFields.find(x => {
                     return l[x] !== l['read_' + x];
                 });
-                if (diff) {
+                if (diff || !l.calc_his) {
                     const data = { id: l.id };
                     // cache read
                     for (const f of costFields.curFields) {
@@ -480,7 +493,7 @@ module.exports = app => {
                     if (fi >= 0) his.splice(fi, 1);
                     const newHis = { ...auditInfo };
                     for (const f of costFields.curFields) {
-                        newHis[f] = data[f];
+                        newHis[f] = l[f];
                     }
                     his.push(newHis);
                     data.calc_his = JSON.stringify(his);

+ 8 - 2
app/service/phase_pay.js

@@ -9,6 +9,7 @@
  */
 
 const audit = require('../const/audit').common;
+const auditType = require('../const/audit').auditType;
 const projectLogConst = require('../const/project_log');
 const shenpiConst = require('../const/shenpi');
 const calcBase = [
@@ -486,8 +487,13 @@ module.exports = app => {
             } else {
                 // 会签,会签人部分审批通过时,只读,但是curSort需按原来的取值
                 phasePay.curSort = phasePay.flowAuditorIds.indexOf(accountId) >= 0 ? phasePay.curAuditors[0].active_order : phasePay.curAuditors[0].active_order - 1;
-                phasePay.readOnly = phasePay.curAuditorIds.indexOf(accountId) < 0;
-                phasePay.canCheck = phasePay.readOnly && phasePay.curAuditorIds.indexOf(accountId) > 0;
+                if (phasePay.curAuditors[0].audit_type === auditType.key.and) {
+                    phasePay.readOnly = !this.ctx.helper._.isEqual(phasePay.flowAuditorIds, phasePay.curAuditorIds);
+                    phasePay.canCheck = true;
+                } else {
+                    phasePay.readOnly = phasePay.curAuditorIds.indexOf(accountId) < 0;
+                    phasePay.canCheck = phasePay.readOnly && phasePay.curAuditorIds.indexOf(accountId) > 0;
+                }
             }
             await this.doCheckCanCancel(phasePay);
         }

+ 7 - 2
app/service/safe_stage.js

@@ -245,8 +245,13 @@ module.exports = app => {
             } else {
                 // 会签,会签人部分审批通过时,只读,但是curSort需按原来的取值
                 safeStage.curSort = safeStage.flowAuditorIds.indexOf(accountId) >= 0 ? safeStage.curAuditors[0].active_order : safeStage.curAuditors[0].active_order - 1;
-                safeStage.readOnly = safeStage.curAuditorIds.indexOf(accountId) < 0;
-                safeStage.canCheck = safeStage.readOnly && safeStage.curAuditorIds.indexOf(accountId) > 0;
+                if (safeStage.curAuditors[0].audit_type === auditType.key.and) {
+                    safeStage.readOnly = !this.ctx.helper._.isEqual(safeStage.flowAuditorIds, safeStage.curAuditorIds);
+                    safeStage.canCheck = true;
+                } else {
+                    safeStage.readOnly = safeStage.curAuditorIds.indexOf(accountId) < 0;
+                    safeStage.canCheck = safeStage.readOnly && safeStage.curAuditorIds.indexOf(accountId) > 0;
+                }
             }
             await this.doCheckCanCancel(safeStage);
         }

+ 23 - 0
app/service/sub_project.js

@@ -20,6 +20,7 @@ const defaultFunRela = {
     needGcl: false,
     budgetZb: true,
     budgetCtrl: true,
+    costAnalysisType: 'book',
 };
 const funSet = require('../const/fun_set');
 const defaultFunSet = funSet.defaultInfo;
@@ -27,6 +28,9 @@ const pageShowConst = require('../const/sp_page_show').defaultSetting;
 const shenpiConst = require('../const/sp_shenpi');
 const defaultShenpi = shenpiConst.defaultInfo.shenpi;
 const arrayInfo = ['shenpi'];
+const ColSetConst = [
+    { key: 'cost_ledger', field: 'cost_ledger_col_set', defaultValue: { cur: 1, cur_excl_tax: 1, end: 1, end_excl_tax: 1} }
+];
 
 class DragTree {
     /**
@@ -738,6 +742,7 @@ module.exports = app => {
                     needGcl: data.needGcl, budgetCtrl: data.budgetCtrl, budgetZb: data.budgetZb,
                     minusNoValue: data.minusNoValue,
                     lockPayExpr: data.lockPayExpr, showMinusCol: data.showMinusCol, ledgerAss: data.ledgerAss,
+                    costAnalysisType: data.costAnalysisType,
                 }),
             });
             return result.affectedRows === 1;
@@ -756,6 +761,24 @@ module.exports = app => {
             return result.affectedRows === 1;
         }
 
+        getColSet(type) {
+            const colSetInfo = ColSetConst.find(x => { return x.key === type; });
+            if (!colSetInfo) return null;
+
+            const result = this.ctx.subProject[colSetInfo.field] ? JSON.parse(this.ctx.subProject[colSetInfo.field]) : {};
+            this.ctx.helper._.defaults(result, colSetInfo.defaultValue);
+            return result;
+        }
+        async updateColSet(type, colSet) {
+            const colSetInfo = ColSetConst.find(x => { return x.key === type; });
+            if (!colSetInfo) throw '未定义的列设置类型';
+
+            const updateData = { id: this.ctx.subProject.id };
+            updateData[colSetInfo.field] = JSON.stringify(colSet);
+            const result = await this.db.update(this.tableName, updateData);
+            return result.affectedRows === 1;
+        }
+
         async saveCommonJson(id, field, datas) {
             const subProject = await this.getDataById(id);
             subProject.common_json = subProject.common_json ? JSON.parse(subProject.common_json) : {};

+ 26 - 4
app/view/contract/detail.ejs

@@ -79,6 +79,9 @@
                             <li class="nav-item">
                                 <a class="nav-link " data-toggle="tab" href="#htfile" role="tab">合同文件</a>
                             </li>
+<!--                            <li class="nav-item">-->
+<!--                                <a class="nav-link " data-toggle="tab" href="#htsupplement" role="tab">补充合同</a>-->
+<!--                            </li>-->
                             <li class="ml-auto">
                                 <!-- 结算合同所有tab可见 ,结算后见解锁合同,2者互斥-->
                                 <a href="#cons-unlock" data-toggle="modal" data-target="#cons-unlock" style="display: none;" class="btn btn-success btn-sm pull-right mr-2">解锁合同</a>
@@ -92,7 +95,8 @@
                                 <a href="javascript:void(0);" id="cancel_contract_btn" style="display: none" class="btn btn-secondary btn-sm pull-right mr-2">取消</a>
                                 <a href="javascript:void(0);" id="save_contract_btn" style="display: none" class="btn btn-primary btn-sm pull-right mr-2">确定</a>
                                 <a href="javascript:void(0);" id="edit_contract_btn" style="display: none" class="btn btn-primary btn-sm pull-right mr-2">编辑合同</a>
-                                <% if (shenpi_status) { %>
+                                <a href="javascript:void(0);" id="add_contract_supplement_btn" style="display: none;" class="btn btn-primary btn-sm pull-right mr-2">新增补充合同</a>
+                            <% if (shenpi_status) { %>
                                     <span class="pull-right mr-2" id="shenpi_btn">
                                     <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm">待上报</a>
                                 </span>
@@ -224,15 +228,15 @@
                             <div class="sp-wrap" style="overflow: auto">
                                 <table class="table table-sm table-bordered" id="htpay-table" style="display: none;">
                                     <thead>
-                                    <tr class="text-center">
+                                    <tr class="text-center" id="pay-header">
                                         <% if (ctx.contract_type === contractConst.type.income) { %>
-                                            <th width="4%">序号</th><th width="5%">回款日期</th><th width="5%">用途</th><th width="7%">回款金额</th><th width="7%">扣款金额</th><th width="7%">应回金额</th><th width="7%">实回金额</th><th width="5%">回款方式</th><th width="5%">创建人</th><th width="9%">创建时间</th><th width="10%">备注</th><th width="4%">附件</th>
+                                            <th width="3%">序号</th><th width="5%" sort="pay_time">回款日期</th><th width="5%">用途</th><th width="7%">回款金额</th><th width="7%">扣款金额</th><th width="7%">应回金额</th><th width="7%">实回金额</th><th width="5%">回款方式</th><th width="5%">创建人</th><th width="9%" sort="create_time">创建时间</th><th width="10%">备注</th><th width="4%">附件</th>
                                             <% if (pay_shenpi_status) { %>
                                                 <th width="15%">审批进度</th>
                                             <% } %>
                                             <th width="10%">操作</th>
                                         <% } else if (ctx.contract_type === contractConst.type.expenses) { %>
-                                            <th width="4%">序号</th><th width="5%">支付日期</th><th width="5%">用途</th><th width="7%">付款金额</th><th width="7%">扣款金额</th><th width="7%">应付金额</th><th width="7%">实付金额</th><th width="5%">支付方式</th><th width="5%">创建人</th><th width="9%">创建时间</th><th width="10%">备注</th><th width="4%">附件</th>
+                                            <th width="3%">序号</th><th width="5%" sort="pay_time">支付日期</th><th width="5%">用途</th><th width="7%">付款金额</th><th width="7%">扣款金额</th><th width="7%">应付金额</th><th width="7%">实付金额</th><th width="5%">支付方式</th><th width="5%">创建人</th><th width="9%" sort="create_time">创建时间</th><th width="10%">备注</th><th width="4%">附件</th>
                                             <% if (pay_shenpi_status) { %>
                                                 <th width="15%">审批进度</th>
                                             <% } %>
@@ -258,6 +262,23 @@
                                 </table>
                             </div>
                         </div>
+                        <div class="tab-pane" id="htsupplement">
+                            <div class="sp-wrap" style="overflow: auto">
+                                <table class="table table-sm table-bordered" id="htsupplement-table" style="display: none;">
+                                    <thead>
+                                    <tr class="text-center" id="supplement-header">
+                                        <th width="3%">序号</th><th width="5%" sort="code">编号</th><th width="10%">名称</th><th width="7%">金额</th><th width="10%">甲方</th><th width="10%">乙方</th><th width="5%">签订时间</th><th width="5%">签订地点</th><th width="5%">创建人</th><th width="9%" sort="create_time">创建时间</th><th width="15%">补充原因及内容</th><th width="4%">附件</th>
+                                        <% if (shenpi_status) { %>
+                                            <th width="15%">审批进度</th>
+                                        <% } %>
+                                        <th width="10%">操作</th>
+                                    </tr>
+                                    </thead>
+                                    <tbody>
+                                    </tbody>
+                                </table>
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>
@@ -300,5 +321,6 @@
     let types = JSON.parse(unescape('<%- escape(JSON.stringify(types)) %>'));
     types.push('');
     let contractPays = [];
+    let contractSupplements = [];
 </script>)
 </script>

+ 81 - 0
app/view/contract/detail_modal.ejs

@@ -249,6 +249,87 @@
         </div>
     </div>
 </div>
+<!--添加补充合同-->
+<div class="modal fade" id="cons-addsupplement" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">新增补充合同</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>编号<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm" name="code" placeholder="请输入编号" type="text">
+                </div>
+                <div class="form-group">
+                    <label>名称<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm" name="name" placeholder="请输入名称" type="text">
+                </div>
+                <div class="form-group">
+                    <label>金额<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm" name="price" placeholder="请输入金额" type="number">
+                </div>
+                <div class="form-group">
+                    <label>甲方</label>
+                    <input class="form-control form-control-sm" name="party_a" placeholder="" type="text">
+                </div>
+                <div class="form-group">
+                    <label>乙方</label>
+                    <input class="form-control form-control-sm" name="party_b" placeholder="" type="text">
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>签订时间</label>
+                    <input class="datepicker-here form-control form-control-sm" name="sign_time" placeholder="点击选择时间" data-date-format="yyyy-MM-dd" data-language="zh" type="text" autocomplete="off">
+                </div>
+                <div class="form-group">
+                    <label>签订地点</label>
+                    <input class="form-control form-control-sm" name="address" placeholder="" type="text">
+                </div>
+                <div class="form-group">
+                    <label>补充原因及内容</label>
+                    <textarea class="form-control form-control-sm" name="content" rows="3"></textarea>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="add-contract-supplement">确定</button>
+                <input type="hidden" name="csid" value="">
+                <button type="button" class="btn btn-sm btn-primary" id="save-contract-supplement" style="display: none">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--附件-->
+<div class="modal fade" id="cons-supplement-file" data-backdrop="static" style="z-index: 1049">
+    <input type="hidden" name="csid">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">附件</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group upload-permission">
+                    <label>单个文件大小限制:50MB,支持<span data-toggle="tooltip" data-placement="bottom" title="doc,docx,xls,xlsx,ppt,pptx,pdf">office等文档格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="jpg,png,bmp">图片格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="rar,zip">压缩包格式</span></label>
+                    <br>
+                    <input type="file" class="" multiple>
+                </div>
+                <div class="modal-height-500" style="overflow:auto;">
+                    <table class="table table-sm table-bordered text-center" style="word-break:break-all; table-layout: fixed">
+                        <thead>
+                        <tr><th width="5%">序号</th><th>名称</th><th width="8%">上传人</th><th width="20%">上传时间</th><th width="15%">操作</th></tr>
+                        </thead>
+                        <tbody>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-outline-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <!--<button type="button" class="btn btn-primary btn-sm" id="upload-file-btn">确定</button>-->
+            </div>
+        </div>
+    </div>
+</div>
 <!--结算合同-->
 <div class="modal fade" id="cons-close" data-backdrop="static">
     <div class="modal-dialog" role="document">

+ 5 - 0
app/view/cost/analysis.ejs

@@ -60,6 +60,8 @@
                 <div class="tab-content">
                     <div id="search" class="tab-pane tab-select-show">
                     </div>
+                    <div id="fujian" class="tab-pane tab-select-show">
+                    </div>
                 </div>
             </div>
         </div>
@@ -69,6 +71,9 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#fujian" href="javascript: void(0);">附件</a>
+                </li>
             </ul>
         </div>
     </div>

+ 7 - 5
app/view/cost/analysis_list.ejs

@@ -21,12 +21,13 @@
                     <tr class="text-center">
                         <th width="80px">期数</th>
                         <th width="70px">报审月份</th>
+                        <th width="70px">关联成本</th>
                         <th width="70px">创建人</th>
-                        <th>项目收入</th>
-                        <th>项目支出</th>
-                        <th>利润</th>
-                        <th>利润率</th>
-                        <th width="180px">审批进度</th>
+                        <th width="200px">项目收入</th>
+                        <th width="200px">项目支出</th>
+                        <th width="200px">利润</th>
+                        <th width="200px">利润率</th>
+                        <th>审批进度</th>
                         <th width="100px">操作</th>
                     </tr>
                     </thead>
@@ -37,6 +38,7 @@
                             <a href="/sp/<%- ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/analysis/<%- s.stage_order %>/stage" target="_blank">第 <%- s.stage_order %> 期</a>
                         </td>
                         <td class="text-center"><%- s.stage_date %></td>
+                        <td class="text-center">第<%- s.rela_stage.sorder %>期</td>
                         <td class="text-center"><%- s.user_name %></td>
                         <td class="text-right"><%- s.stage_tp.in_tp %></td>
                         <td class="text-right"><%- s.stage_tp.out_tp %></td>

+ 2 - 1
app/view/cost/analysis_list_modal.ejs

@@ -11,7 +11,7 @@
                     <input type="hidden" value="<%- (stages.length + 1) %>" name="stage_order">
                 </div>
                 <div class="form-group form-group-sm">
-                    <label>关联成本</label>
+                    <label>关联成本(<%- analysisTypeStr %>)</label>
                     <select class="form-control form-control-sm" name="stage">
                         <% for (const s of validRelaStages) { %>
                         <option value="<%- s.stage_order %>">第 <%- s.stage_order %> 期</option>
@@ -22,6 +22,7 @@
             <div class="modal-footer">
                 <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
                 <input type="hidden" name="stage_type" value="<%- stage_type %>" />
+                <input type="hidden" name="analysis_type" value="<%- analysisType %>" />
                 <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
                 <button type="submit" class="btn btn-sm btn-primary">确定</button>
             </div>

+ 2 - 1
app/view/cost/analysis_menu_list.ejs

@@ -1,3 +1,4 @@
 <nav-menu title="返回" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/analysis" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
-<nav-menu title="成本分析" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/analysis/<%- ctx.costStage.stage_order %>/stage" ml="3" active="<%= (ctx.url.indexOf('ledger') >= 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="成本分析" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/analysis/<%- ctx.costStage.stage_order %>/stage" ml="3" active="<%= (ctx.url.indexOf('stage') >= 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="审核比较" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/analysis/<%- ctx.costStage.stage_order %>/compare" ml="3" active="<%= (ctx.url.indexOf('compare') >= 0 ? 1 : -1) %>"></nav-menu>
 <% include ./audit_btn.ejs %>

+ 1 - 0
app/view/cost/analysis_modal.ejs

@@ -1,5 +1,6 @@
 <% include ../shares/delete_hint_modal.ejs %>
 <% include ./audit_modal.ejs %>
+<% include ../shares/upload_att.ejs %>
 <div class="modal" id="import-pay" data-backdrop="static" aria-modal="true" role="dialog">
     <div class="modal-dialog" role="document">
         <div class="modal-content">

+ 3 - 1
app/view/cost/audit_modal.ejs

@@ -829,6 +829,8 @@
     const shenpiConst =  JSON.parse('<%- JSON.stringify(shenpiConst) %>');
     const createUserId = parseInt('<%= ctx.costStage.create_user_id %>');
     const auditType = JSON.parse(unescape('<%- escape(JSON.stringify(auditType)) %>'));
+    const csrf = '<%= ctx.csrf %>';
+    const authMobile = '<%= authMobile %>';
 </script>
 <script>
     $('[name=stage-start]').submit(function (e) {
@@ -895,4 +897,4 @@
         <% } %>
         $('#hide-all').hide();
     });
-</script>
+</script>

+ 23 - 11
app/view/cost/book_list.ejs

@@ -19,16 +19,24 @@
                 <table class="table table-bordered table-hover">
                     <thead>
                     <tr class="text-center">
-                        <th width="80px">期数</th>
-                        <th width="70px">报审月份</th>
-                        <th width="70px">关联成本</th>
-                        <th width="70px">创建人</th>
-                        <th>本期入账金额</th>
-                        <th>截止本期入账金额</th>
-                        <th>本期入账金额(不含税)</th>
-                        <th>截止本期入账金额(不含税)</th>
-                        <th width="180px">审批进度</th>
-                        <th width="100px">操作</th>
+                        <th width="80px" rowspan="2">期数</th>
+                        <th width="70px" rowspan="2">报审月份</th>
+                        <th width="70px" rowspan="2">关联成本</th>
+                        <th width="70px" rowspan="2">创建人</th>
+                        <th width="120px" colspan="4">本期</th>
+                        <th width="120px" colspan="4">截止本期</th>
+                        <th rowspan="2">审批进度</th>
+                        <th width="100px" rowspan="2">操作</th>
+                    </tr>
+                    <tr class="text-center">
+                        <th width="130px">应付金额</th>
+                        <th width="130px">实付金额</th>
+                        <th width="130px">入账金额</th>
+                        <th width="130px">入账金额(不含税)</th>
+                        <th width="130px">应付金额</th>
+                        <th width="130px">实付金额</th>
+                        <th width="130px">入账金额</th>
+                        <th width="130px">入账金额(不含税)</th>
                     </tr>
                     </thead>
                     <tbody>
@@ -40,9 +48,13 @@
                         <td class="text-center"><%- s.stage_date %></td>
                         <td class="text-center">第<%- s.rela_stage.sorder %>期</td>
                         <td class="text-center"><%- s.user_name %></td>
+                        <td class="text-right"><%- s.relaStage.stage_tp.yf_tp %></td>
+                        <td class="text-right"><%- s.relaStage.stage_tp.sf_tp %></td>
                         <td class="text-right"><%- s.stage_tp.in_tp %></td>
-                        <td class="text-right"><%- s.stage_end_tp.in_tp %></td>
                         <td class="text-right"><%- s.stage_tp.in_excl_tax_tp %></td>
+                        <td class="text-right"><%- s.relaStage.stage_end_tp.yf_tp %></td>
+                        <td class="text-right"><%- s.relaStage.stage_end_tp.sf_tp %></td>
+                        <td class="text-right"><%- s.stage_end_tp.in_tp %></td>
                         <td class="text-right"><%- s.stage_end_tp.in_excl_tax_tp %></td>
                         <td class="<%- auditConst.info[s.audit_status].class %>">
                             <% if (s.audit_status === auditConst.status.checked && s.final_auditor_str) { %>

+ 1 - 1
app/view/cost/book_list_modal.ejs

@@ -12,7 +12,7 @@
                 </div>
                 <div class="form-group form-group-sm">
                     <label>关联成本</label>
-                    <select class="form-control form-control-sm" name="stage">
+                    <select class="form-control form-control-sm" name="stage" readonly="">
                         <% for (const s of validLedgerStages) { %>
                         <option value="<%- s.stage_order %>">第 <%- s.stage_order %> 期</option>
                         <% } %>

+ 2 - 1
app/view/cost/book_menu_list.ejs

@@ -1,3 +1,4 @@
 <nav-menu title="返回" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/book" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
-<nav-menu title="财务账面" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/book/<%- ctx.costStage.stage_order %>/stage" ml="3" active="<%= (ctx.url.indexOf('ledger') >= 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="财务账面" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/book/<%- ctx.costStage.stage_order %>/stage" ml="3" active="<%= (ctx.url.indexOf('stage') >= 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="审核比较" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/book/<%- ctx.costStage.stage_order %>/compare" ml="3" active="<%= (ctx.url.indexOf('compare') >= 0 ? 1 : -1) %>"></nav-menu>
 <% include ./audit_btn.ejs %>

+ 205 - 0
app/view/cost/compare.ejs

@@ -0,0 +1,205 @@
+<% include ./stage_memu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./stage_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap row pr-46">
+        <div class="c-header p-0 col-12">
+        </div>
+        <!--核心内容(两栏)-->
+        <div class="row w-100 sub-content">
+            <!--左栏-->
+            <div class="c-body" id="left-view" style="width: 100%">
+                <div class="sjs-height-1" style="overflow: hidden" id="bills-spread">
+                </div>
+                <div class="bcontent-wrap">
+                    <div class="resize-y" id="detail-spr" r-Type="height" div1=".sjs-height-1" div2=".bcontent-wrap" r-parent="div2" title="调整大小"><!--调整上下高度条--></div>
+                    <div class="bc-bar mb-1">
+                        <ul class="nav nav-tabs">
+                            <li class="nav-item">
+                                <a class="nav-link active" href="javascript:void(0)">明细数据</a>
+                            </li>
+                        </ul>
+                    </div>
+                    <div class="sp-wrap" id="detail-spread">
+                    </div>
+                </div>
+            </div>
+            <div class="c-body" id="right-view" style="display: none; width: 33%;">
+                <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
+                <div class="tab-content">
+                    <div id="search" class="tab-pane tab-select-show">
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!--右侧菜单-->
+        <div class="side-menu">
+            <ul class="nav flex-column right-nav" id="side-menu">
+                <li class="nav-item">
+                    <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
+<script>
+    const tenderId = parseInt('<%- ctx.tender.id %>');
+    const stageId = parseInt('<%- ctx.costStage.id %>');
+    const stageType = parseInt('<%- ctx.costStage.stage_type %>');
+    const stageDate = '<%- ctx.costStage.stage_date %>';
+    <% if (ctx.costStage.stage_type === 'ledger') { %>
+    const bllsSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 230, formatter: '@', cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', cellType: 'autoTip'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+            {title: '税率(%)', colSpan: '1', rowSpan: '2', field: 'tax', hAlign: 2, width: 80, type: 'Number'},
+            {title: '本期|付款金额', colSpan: '4|1', rowSpan: '1|1', field: 'pay_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|扣款金额', colSpan: '|1', rowSpan: '|1', field: 'cut_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|应付金额', colSpan: '|1', rowSpan: '|1', field: 'yf_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '本期(不含税)|应付金额', colSpan: '2|1', rowSpan: '1|1', field: 'yf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
+            {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        headColWidth: [50],
+        tipsSum: true,
+        readOnly: true,
+    };
+    const dealSpreadSetting = {
+        cols: [
+            {title: '合同编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 160, formatter: '@'},
+            {title: '合同名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 160, formatter: '@'},
+            {title: '乙方', colSpan: '1', rowSpan: '2', field: 'party_b', hAlign: 0, width: 150, formatter: '@'},
+            {title: '支付年月', colSpan: '1', rowSpan: '2', field: 'pay_date', hAlign: 1, width: 120, formatter: '@'},
+            {title: '税率(%)', colSpan: '1', rowSpan: '2', field: 'tax', hAlign: 2, width: 80, type: 'Number'},
+            {title: '本期|付款金额', colSpan: '4|1', rowSpan: '1|1', field: 'pay_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|扣款金额', colSpan: '|1', rowSpan: '|1', field: 'cut_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|应付金额', colSpan: '|1', rowSpan: '|1', field: 'yf_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '本期(不含税)|应付金额', colSpan: '2|1', rowSpan: '1|1', field: 'yf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
+            {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        headColWidth: [50],
+        tipsSum: true,
+    };
+    const commonSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 160, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 160, formatter: '@'},
+            {title: '税率(%)', colSpan: '1', rowSpan: '2', field: 'tax', hAlign: 2, width: 80, type: 'Number'},
+            {title: '本期|付款金额', colSpan: '4|1', rowSpan: '1|1', field: 'pay_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|扣款金额', colSpan: '|1', rowSpan: '|1', field: 'cut_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|应付金额', colSpan: '|1', rowSpan: '|1', field: 'yf_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '本期(不含税)|应付金额', colSpan: '2|1', rowSpan: '1|1', field: 'yf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
+            {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        emptyRows: 3,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        headColWidth: [50],
+        tipsSum: true,
+    };
+    <% } %>
+    <% if (ctx.costStage.stage_type === 'book') { %>
+    const bllsSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 230, formatter: '@', cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 185, formatter: '@', cellType: 'autoTip'},
+            {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+            {title: '税率(%)', colSpan: '1', rowSpan: '1', field: 'tax', hAlign: 2, width: 80, type: 'Number'},
+            {title: '入账金额', colSpan: '1', rowSpan: '1', field: 'in_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '入账金额(不含税)', colSpan: '1', rowSpan: '1', field: 'in_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        headColWidth: [50],
+        tipsSum: true,
+        readOnly: true,
+    };
+    const dealSpreadSetting = {
+        cols: [
+            {title: '合同编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 160, formatter: '@'},
+            {title: '合同名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 160, formatter: '@'},
+            {title: '乙方', colSpan: '1', rowSpan: '1', field: 'party_b', hAlign: 0, width: 150, formatter: '@'},
+            {title: '支付年月', colSpan: '1', rowSpan: '1', field: 'pay_date', hAlign: 1, width: 120, formatter: '@'},
+            {title: '税率(%)', colSpan: '1', rowSpan: '1', field: 'tax', hAlign: 2, width: 80, type: 'Number'},
+            {title: '入账金额', colSpan: '1', rowSpan: '1', field: 'in_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '入账金额(不含税)', colSpan: '1', rowSpan: '1', field: 'in_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        headColWidth: [50],
+        tipsSum: true,
+    };
+    const commonSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 160, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 160, formatter: '@'},
+            {title: '税率(%)', colSpan: '1', rowSpan: '1', field: 'tax', hAlign: 2, width: 80, type: 'Number'},
+            {title: '入账金额', colSpan: '1', rowSpan: '1', field: 'in_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '入账金额(不含税)', colSpan: '1', rowSpan: '1', field: 'in_excl_tax_tp', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        emptyRows: 3,
+        headRows: 1,
+        headRowHeight: [25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        headColWidth: [50],
+        tipsSum: true,
+    };
+    <% } %>
+    <% if (ctx.costStage.stage_type === 'analysis') { %>
+    const bllsSpreadSetting = JSON.parse(unescape('<%- escape(JSON.stringify( ctx.costStage.calcTemplate.spread_cache )) %>'));
+    const dealSpreadSetting = JSON.parse(unescape('<%- escape(JSON.stringify( ctx.costStage.calcTemplate.sub_spread_cache.deal )) %>'));
+    const commonSpreadSetting = JSON.parse(unescape('<%- escape(JSON.stringify( ctx.costStage.calcTemplate.sub_spread_cache.common )) %>'));
+    <% } %>
+    const calcFields = JSON.parse('<%- JSON.stringify(calcFields) %>');
+</script>

+ 1 - 0
app/view/cost/compare_modal.ejs

@@ -0,0 +1 @@
+<% include ./audit_modal.ejs %>

+ 33 - 0
app/view/cost/gather.ejs

@@ -0,0 +1,33 @@
+<% include ./list_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./list_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="ml-auto">
+                <button href="#cate-set" class="btn btn-sm btn-light text-primary" data-toggle="modal" data-target="#select-qi"><i class="fa fa-clone"></i> 选择期</button>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" id="ledger-gather-spread">
+            </div>
+        </div>
+    </div>
+</div>

+ 46 - 0
app/view/cost/gather_modal.ejs

@@ -0,0 +1,46 @@
+<div class="modal fade" id="select-qi" 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 scroll-y modal-height-500">
+                <table class="table table-sm" >
+                    <tr class="text-center"><th>期</th><th>报审年月</th><th width="60">选择</th></tr>
+                    <% for (const s of ledgerStages) { %>
+                    <tr stage-id="<%- s.id %>"><td><%- s.stage_order %>期</td><td><%- s.stage_date %></td><td><input type="checkbox"></td></tr>
+                    <% } %>
+                </table>
+            </div>
+            <div class="modal-body">
+                <div class="d-flex">
+                    <div class="form-check form-check-inline">汇总方式:</div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="gather-type" id="gather1" value="stage" checked>
+                        <label class="form-check-label" for="gather1">期</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="gather-type" id="gather2" value="month">
+                        <label class="form-check-label" for="gather2">月份</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="gather-type" id="gather3" value="quarter">
+                        <label class="form-check-label" for="gather3">季度</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="gather-type" id="gather4" value="year">
+                        <label class="form-check-label" for="gather4">年度</label>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <div class="form-check form-check-inline">
+                    <input class="form-check-input" type="checkbox" id="select-qi-all">
+                    <label class="form-check-label" for="select-qi-all">全选</label>
+                </div>
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="select-qi-ok">确认</button>
+            </div>
+        </div>
+    </div>
+</div>

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

@@ -30,6 +30,7 @@
                 <% } %>
             </div>
             <div class="ml-auto">
+                <a href="#cost-ledger-col" data-toggle="modal" data-target="#cost-ledger-col" class="btn btn-primary btn-sm">列设置</a>
             </div>
         </div>
     </div>
@@ -89,4 +90,5 @@
     const stageId = parseInt('<%- ctx.costStage.id %>');
     const stageDate = '<%- ctx.costStage.stage_date %>';
     const costStageComplete = <%- ctx.costStage.audit_status === auditConst.status.checked %>;
+    const colSet = JSON.parse('<%- JSON.stringify(colSet) %>');
 </script>

+ 47 - 11
app/view/cost/ledger_list.ejs

@@ -6,11 +6,12 @@
             <h2>
                 审批列表
             </h2>
-            <% if (ctx.permission.cost.ledger_add && (stages.length === 0 || stages[0].audit_status === auditConst.status.checked)) { %>
             <div class="ml-auto">
+                <a href="#cost-ledger-col" data-toggle="modal" data-target="#cost-ledger-col" class="btn btn-primary btn-sm">列设置</a>
+                <% if (ctx.permission.cost.ledger_add && (stages.length === 0 || stages[0].audit_status === auditConst.status.checked)) { %>
                 <a href="#add-qi" data-toggle="modal" data-target="#add-qi" class="btn btn-primary btn-sm">新建报审</a>
+                <% } %>
             </div>
-            <% } %>
         </div>
     </div>
     <div class="content-wrap">
@@ -21,20 +22,42 @@
                     <tr class="text-center">
                         <th width="80px" rowspan="2">期数</th>
                         <th width="70px" rowspan="2">报审月份</th>
+                        <% if (colSet.cur) { %>
                         <th colspan="4">本期金额</th>
+                        <% } %>
+                        <% if (colSet.cur_excl_tax) { %>
+                        <th colspan="2">本期金额(不含税)</th>
+                        <% } %>
+                        <% if (colSet.end) { %>
                         <th colspan="4">截止本期金额</th>
-                        <th width="120px" rowspan="2">审批进度</th>
+                        <% } %>
+                        <% if (colSet.end_excl_tax) { %>
+                        <th colspan="2">截止本期金额(不含税)</th>
+                        <% } %>
+                        <th rowspan="2">审批进度</th>
                         <th width="100px" rowspan="2">操作</th>
                     </tr>
                     <tr class="text-center">
-                        <th>付款</th>
-                        <th>扣款</th>
-                        <th>应付</th>
-                        <th>实付</th>
-                        <th>付款</th>
-                        <th>扣款</th>
-                        <th>应付</th>
-                        <th>实付</th>
+                        <% if (colSet.cur) { %>
+                        <th width="100px" name="tp-col">付款</th>
+                        <th width="100px" name="tp-col">扣款</th>
+                        <th width="100px" name="tp-col">应付</th>
+                        <th width="100px" name="tp-col">实付</th>
+                        <% } %>
+                        <% if (colSet.cur_excl_tax) { %>
+                        <th width="100px" name="tp-col">应付</th>
+                        <th width="100px" name="tp-col">实付</th>
+                        <% } %>
+                        <% if (colSet.end) { %>
+                        <th width="100px" name="tp-col">付款</th>
+                        <th width="100px" name="tp-col">扣款</th>
+                        <th width="100px" name="tp-col">应付</th>
+                        <th width="100px" name="tp-col">实付</th>
+                        <% } %>
+                        <% if (colSet.end_excl_tax) { %>
+                        <th width="100px" name="tp-col">应付</th>
+                        <th width="100px" name="tp-col">实付</th>
+                        <% } %>
                     </tr>
                     </thead>
                     <tbody>
@@ -44,14 +67,26 @@
                             <a href="/sp/<%- ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/ledger/<%- s.stage_order %>/stage" target="_blank">第 <%- s.stage_order %> 期</a>
                         </td>
                         <td class="text-center"><%- s.stage_date %></td>
+                        <% if (colSet.cur) { %>
                         <td class="text-right"><%- s.stage_tp.pay_tp %></td>
                         <td class="text-right"><%- s.stage_tp.cut_tp %></td>
                         <td class="text-right"><%- s.stage_tp.yf_tp %></td>
                         <td class="text-right"><%- s.stage_tp.sf_tp %></td>
+                        <% } %>
+                        <% if (colSet.cur_excl_tax) { %>
+                        <td class="text-right"><%- s.stage_tp.yf_excl_tax_tp %></td>
+                        <td class="text-right"><%- s.stage_tp.sf_excl_tax_tp %></td>
+                        <% } %>
+                        <% if (colSet.end) { %>
                         <td class="text-right"><%- s.stage_end_tp.pay_tp %></td>
                         <td class="text-right"><%- s.stage_end_tp.cut_tp %></td>
                         <td class="text-right"><%- s.stage_end_tp.yf_tp %></td>
                         <td class="text-right"><%- s.stage_end_tp.sf_tp %></td>
+                        <% } %>
+                        <% if (colSet.end_excl_tax) { %>
+                        <td class="text-right"><%- s.stage_end_tp.yf_excl_tax_tp %></td>
+                        <td class="text-right"><%- s.stage_end_tp.sf_excl_tax_tp %></td>
+                        <% } %>
                         <td class="<%- auditConst.info[s.audit_status].class %>">
                             <% if (s.audit_status === auditConst.status.checked && s.final_auditor_str) { %>
                             <a href="#sp-list" data-toggle="modal" data-target="#sp-list" stage-order="<%- s.stage_order %>"><%- s.final_auditor_str %></a>
@@ -89,4 +124,5 @@
     const stages = JSON.parse('<%- JSON.stringify(stages) %>');
     const auditType = JSON.parse('<%- JSON.stringify(auditType) %>');
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
+    if ($('[name="tp-col"]').length > 0) $('[name="tp-col"]').attr('width', (1200/$('[name="tp-col"]').length) + 'px')
 </script>

+ 45 - 0
app/view/cost/ledger_list_modal.ejs

@@ -49,6 +49,43 @@
         </div>
     </div>
 </div>
+<div class="modal" id="cost-ledger-col" data-backdrop="static" aria-modal="true" role="dialog">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="colSet" method="POST" onsubmit="return convertColSet();">
+            <div class="modal-header">
+                <h5 class="modal-title">列设置</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group form-check mb-0">
+                    <input type="checkbox" class="form-check-input group-checkbox" id="col_cur" <% if (colSet.cur) { %> checked <%} %>>
+                    <label class="form-check-label" for="col_cur">本期</label>
+                </div>
+                <div class="form-group form-check mb-0">
+                    <input type="checkbox" class="form-check-input group-checkbox" id="col_cur_excl_tax" <% if (colSet.cur_excl_tax) { %> checked <%} %>>
+                    <label class="form-check-label" for="col_cur_excl_tax">本期(不含税)</label>
+                </div>
+                <div class="form-group form-check mb-0">
+                    <input type="checkbox" class="form-check-input group-checkbox" id="col_end" <% if (colSet.end) { %> checked <%} %>>
+                    <label class="form-check-label" for="col_end">截止本期</label>
+                </div>
+                <div class="form-group form-check mb-0">
+                    <input type="checkbox" class="form-check-input group-checkbox" id="col_end_excl_tax" <% if (colSet.end_excl_tax) { %> checked <%} %>>
+                    <label class="form-check-label" for="col_end_excl_tax">截止本期(不含税)</label>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="col_cur" value="<%- colSet.cur %>" />
+                <input type="hidden" name="col_cur_excl_tax" value="<%- colSet.cur_excl_tax %>" />
+                <input type="hidden" name="col_end" value="<%- colSet.end %>" />
+                <input type="hidden" name="col_end_excl_tax" value="<%- colSet.end_excl_tax %>" />
+                <input type="hidden" name="stage_type" value="<%- stage_type %>" />
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="submit" class="btn btn-sm btn-primary">确定</button>
+            </div>
+        </form>
+    </div>
+</div>
 <script>
     $('.datepicker-here').datepicker({
         autoClose: true,
@@ -65,4 +102,12 @@
             return false;
         }
     }
+    const convertColSet = function() {
+        const check = $('input[type=checkbox]', '#cost-ledger-col');
+        for (const c of check) {
+            const id = c.getAttribute('id');
+            $(`input[name=${id}]`).val(c.checked ? 1 : 0);
+        }
+        return true;
+    }
 </script>

+ 2 - 1
app/view/cost/ledger_menu_list.ejs

@@ -1,3 +1,4 @@
 <nav-menu title="返回" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/ledger" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
-<nav-menu title="成本审批" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/ledger/<%- ctx.costStage.stage_order %>/stage" ml="3" active="<%= (ctx.url.indexOf('ledger') >= 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="成本报审" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/ledger/<%- ctx.costStage.stage_order %>/stage" ml="3" active="<%= (ctx.url.indexOf('stage') >= 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="审核比较" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/ledger/<%- ctx.costStage.stage_order %>/compare" ml="3" active="<%= (ctx.url.indexOf('compare') >= 0 ? 1 : -1) %>"></nav-menu>
 <% include ./audit_btn.ejs %>

+ 47 - 0
app/view/cost/ledger_modal.ejs

@@ -66,5 +66,52 @@
         </div>
     </div>
 </div>
+<div class="modal" id="cost-ledger-col" data-backdrop="static" aria-modal="true" role="dialog">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="/sp/<%- ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/colSet" method="POST" onsubmit="return convertColSet();">
+            <div class="modal-header">
+                <h5 class="modal-title">列设置</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group form-check mb-0">
+                    <input type="checkbox" class="form-check-input group-checkbox" id="col_cur" <% if (colSet.cur) { %> checked <%} %>>
+                    <label class="form-check-label" for="col_cur">本期</label>
+                </div>
+                <div class="form-group form-check mb-0">
+                    <input type="checkbox" class="form-check-input group-checkbox" id="col_cur_excl_tax" <% if (colSet.cur_excl_tax) { %> checked <%} %>>
+                    <label class="form-check-label" for="col_cur_excl_tax">本期(不含税)</label>
+                </div>
+                <div class="form-group form-check mb-0">
+                    <input type="checkbox" class="form-check-input group-checkbox" id="col_end" <% if (colSet.end) { %> checked <%} %>>
+                    <label class="form-check-label" for="col_end">截止本期</label>
+                </div>
+                <div class="form-group form-check mb-0">
+                    <input type="checkbox" class="form-check-input group-checkbox" id="col_end_excl_tax" <% if (colSet.end_excl_tax) { %> checked <%} %>>
+                    <label class="form-check-label" for="col_end_excl_tax">截止本期(不含税)</label>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="col_cur" value="<%- colSet.cur %>" />
+                <input type="hidden" name="col_cur_excl_tax" value="<%- colSet.cur_excl_tax %>" />
+                <input type="hidden" name="col_end" value="<%- colSet.end %>" />
+                <input type="hidden" name="col_end_excl_tax" value="<%- colSet.end_excl_tax %>" />
+                <input type="hidden" name="stage_type" value="<%- ctx.costStage.stage_type %>" />
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="submit" class="btn btn-sm btn-primary">确定</button>
+            </div>
+        </form>
+    </div>
+</div>
+<script>
+    const convertColSet = function() {
+        const check = $('input[type=checkbox]', '#cost-ledger-col');
+        for (const c of check) {
+            const id = c.getAttribute('id');
+            $(`input[name=${id}]`).val(c.checked ? 1 : 0);
+        }
+        return true;
+    }
+</script>
 <link href="/public/css/bootstrap/select2.min.css" rel="stylesheet" />
 <script src="/public/js/bootstrap/select2.min.js"></script>

+ 1 - 1
app/view/cost/list_menu_list.ejs

@@ -2,7 +2,7 @@
 <nav-menu title="成本报审" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/ledger" ml="3" active="<%= (ctx.url.indexOf('ledger') >= 0 ? 1 : -1) %>"></nav-menu>
 <nav-menu title="财务账面" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/book" ml="3" active="<%= (ctx.url.indexOf('book') >= 0 ? 1 : -1) %>"></nav-menu>
 <nav-menu title="成本分析" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/analysis" ml="3" active="<%= (ctx.url.indexOf('analysis') >= 0 ? 1 : -1) %>"></nav-menu>
-<!--<nav-menu title="汇总分析" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/gather" ml="3" active="<%= (ctx.url.indexOf('gather') >= 0 ? 1 : -1) %>"></nav-menu>-->
+<nav-menu title="汇总分析" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/gather" ml="3" active="<%= (ctx.url.indexOf('gather') >= 0 ? 1 : -1) %>"></nav-menu>
 <!--<nav-menu title="输出报表" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/report" ml="3" active="<%= (ctx.url.indexOf('report') >= 0 ? 1 : -1) %>"></nav-menu>-->
 <% if (ctx.session.sessionUser.is_admin) { %>
 <nav-menu title="审批流程" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/flow" ml="3" active="<%= (ctx.url.indexOf('flow') >= 0 ? 1 : -1) %>"></nav-menu>

+ 3 - 3
app/view/dashboard/index.ejs

@@ -382,7 +382,7 @@
                                         </tr>
                                         <% } %>
                                         <% for (const acs of auditCostStageBook) { %>
-                                        <tr data-type="16">
+                                        <tr data-type="17">
                                             <td><span class="bg-new-coststage text-new-coststage badge text-width">财务账面</span></td>
                                             <td><a href="/sp/<%- acs.spid %>/cost/tender/<%- acs.tid %>/book"><%- acs.name %></a> <a href="/sp/<%- acs.spid %>/cost/tender/<%- acs.tid %>/book/<%- acs.stage_order %>/stage">第<%- acs.stage_order %>期</a></td>
                                             <td>第<%- acs.stage_order %>期</td>
@@ -391,7 +391,7 @@
                                         </tr>
                                         <% } %>
                                         <% for (const acs of auditCostStageAnalysis) { %>
-                                        <tr data-type="16">
+                                        <tr data-type="18">
                                             <td><span class="bg-new-coststage text-new-coststage badge text-width">成本分析</span></td>
                                             <td><a href="/sp/<%- acs.spid %>/cost/tender/<%- acs.tid %>/analysis"><%- acs.name %></a> <a href="/sp/<%- acs.spid %>/cost/tender/<%- acs.tid %>/analysis/<%- acs.stage_order %>/stage">第<%- acs.stage_order %>期</a></td>
                                             <td>第<%- acs.stage_order %>期</td>
@@ -692,7 +692,7 @@
                                                     <td class="<%- acCostStage.statusClass[notice.status] %>"><%- acCostStage.statusString[notice.status] %></td>
                                                     <td><%- notice.opinion ? notice.opinion : '' %></td>
                                                 </tr>
-                                            <% } else if(notice.type === pushType.costStageLedger && ctx.subProject.page_show.cost) { %>
+                                            <% } else if(notice.type === pushType.costStageBook && ctx.subProject.page_show.cost) { %>
                                                 <tr data-type="17">
                                                     <td><span class="bg-new-coststage text-new-coststage badge text-width">财务账面</span></td>
                                                     <td><a href="/sp/<%- notice.spid %>/cost/tender/<%- notice.tid %>/book"><%- notice.name %></a> <a href="/sp/<%- notice.spid %>/cost/tender/<%- notice.tid %>/book/<%- notice.order %>/stage">第<%- notice.order %>期</a></td>

+ 6 - 1
app/view/ledger/gather.ejs

@@ -93,6 +93,10 @@
                                                 <input class="form-check-input" type="checkbox" id="gather-anc-gcl-xm" value="jldy" checked name="gather-anc-gcl">
                                                 <label class="form-check-label" for="gather-anc-gcl-xm">细目</label>
                                             </div>
+                                            <div class="form-check form-check-inline">
+                                                <input class="form-check-input" type="checkbox" id="gather-anc-gcl-jldy" value="bwmx" name="gather-anc-gcl">
+                                                <label class="form-check-label" for="gather-anc-gcl-jldy">计量单元</label>
+                                            </div>
                                         </div>
                                     </div>
                                 </div>
@@ -155,4 +159,5 @@
     const isTz = <%- ctx.tender.data.measure_type === measureType.tz.value %>;
     const chapter = JSON.parse('<%- JSON.stringify(ctx.tender.info.chapter) %>');
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
-</script>
+</script>
+<script src="/public/js/PinYinOrder.bundle.js"></script>

+ 2 - 2
app/view/setting/limit.ejs

@@ -10,7 +10,7 @@
                 <div class="col-2">
                     <div class="d-flex flex-row">
                         <form class="ml-2 p-2" method="POST" action="/setting/limit/save">
-                            <input type="hidden" name="_csrf_j2" value="<%= ctx.csrf %>" />
+                            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
                             <button class="btn btn-sm btn-light text-primary"><i class="fa fa-plus" aria-hidden="true" type="submit"></i> 新增配置</button>
                         </form>
                     </div>
@@ -50,7 +50,7 @@
     </div>
     <div stype="display: none">
         <form id="hiddenForm" action="" method="POST">
-            <input type="hidden" name="_csrf_j2" value="<%= ctx.csrf %>" />
+            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
             <input type="hidden" id="extra" name="" value="">
         </form>
     </div>

+ 2 - 2
app/view/setting/s2b.ejs

@@ -172,12 +172,12 @@
                                     <td class="text-center">
                                         <div class="form-check form-check-inline">
                                             <input class="form-check-input" type="checkbox" id="inlineCheckbox5" <% if (t.s2b_multi_check) { %>checked<% } %> name="multi_check" onchange="updateS2bSetting(this);" value="<%- t.s2b_multi_check %>">
-                                            <input type="hidden" name="_csrf_j2" value="<%= ctx.csrf %>">
+                                            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
                                             <label class="form-check-label" for="inlineCheckbox5">检查计量</label>
                                         </div>
                                         <div class="form-check form-check-inline">
                                             <input class="form-check-input" type="checkbox" id="inlineCheckbox6" <% if (t.s2b_multi_limit) { %>checked<% } %> name="multi_limit" onchange="updateS2bSetting(this);" value="<%- t.s2b_multi_limit %>">
-                                            <input type="hidden" name="_csrf_j2" value="<%= ctx.csrf %>">
+                                            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
                                             <label class="form-check-label" for="inlineCheckbox6">限制上报</label>
                                         </div>
                                         <div class="form-check form-check-inline">

+ 25 - 1
app/view/sp_setting/fun.ejs

@@ -357,7 +357,7 @@
                         </div>
                         <div class="card mb-3">
                             <div class="card-header d-flex justify-content-between">
-                                <div>动态决算</div>
+                                <div>项目进度</div>
                             </div>
                             <div class="card-body">
                                 <div class="form-group mb-4">
@@ -385,6 +385,29 @@
                                 </div>
                             </div>
                         </div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="card mb-3">
+                                    <div class="card-header d-flex justify-content-between">
+                                        <div>成本管理</div>
+                                    </div>
+                                    <div class="card-body">
+                                        <div class="alert alert-dark py-1 px-2 mb-2" role="alert">成本分析-关联成本类型</div>
+                                        <div class="d-flex">
+                                            <div class="form-check form-check-inline">
+                                                <input class="form-check-input" type="radio" id="radio_ledger" value="ledger" <% if (funRela.costAnalysisType === 'ledger') { %>checked<% } %> name="cost_analysis_type" onchange="updateSetting();">
+                                                <label class="form-check-label" for="radio_ledger">成本报审</label>
+                                            </div>
+                                            <div class="form-check form-check-inline">
+                                                <input class="form-check-input" type="radio" id="radio_book" value="book" <% if (funRela.costAnalysisType === 'book') { %>checked<% } %> name="cost_analysis_type" onchange="updateSetting();">
+                                                <label class="form-check-label" for="radio_book">财务账面</label>
+                                            </div>
+                                            <label class="form-text alert alert-warning py-1 px-2 mb-2"><i class="fa fa-exclamation-circle mr-1"></i>切换关联成本,对已创建的成本分析期数据无效。</label>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>
@@ -647,6 +670,7 @@
         }
         postData('/sp/<%- ctx.subProject.id %>/setting/fun/update', {
             imType: parseInt($('[name=im_type]:checked').val()),
+            costAnalysisType: $('[name=cost_analysis_type]:checked').val(),
             banOver: $('[name=ban_over]')[0].checked,
             hintOver: $('#hint_over')[0].checked,
             banMinusChangeBills: $('[name=ban_minus_cb]')[0].checked,

+ 27 - 0
app/view/sp_setting/manage_modal.ejs

@@ -402,3 +402,30 @@
         </div>
     </div>
 </div>
+<div class="modal fade" id="multi" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="multi-title">分组审批</h5>
+            </div>
+            <div class="modal-body">
+                <div class="modal-height-500" style="overflow: auto;" id="multi-spread">
+                </div>
+                <div name="set-hint" htype="<%- auditType.key.multi %>">
+                    顺序:从1开始,务必连续,某审批人审批通过时,将会跳过其前面所有的审批人。<br/>
+                    受限:受限时,前面所有的审批人都审批通过后,方可审批。
+                </div>
+                <div name="set-hint" htype="<%- auditType.key.and %>">
+                    顺序:从1开始,务必连续,某审批人审批通过时,将会跳过其前面所有的审批人。
+                </div>
+                <div name="set-hint" htype="<%- auditType.key.or %>">
+                    最少审批:该审批人审批通过时,审批通过人数满足则结束本流程。
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="multi-ok">保存</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 1 - 1
config/config.default.js

@@ -276,6 +276,6 @@ module.exports = appInfo => {
         algorithm: 'aes-256-cbc',
     };
 
-    config.kkfileUrl = 'http://jlqa.smartcost.com.cn:8012/onlinePreview?url=';
+    config.kkfileUrl = '/preview/onlinePreview?url=';
     return config;
 };

+ 44 - 0
config/web.js

@@ -1890,6 +1890,7 @@ const JsFiles = {
                     '/public/js/shares/cs_tools.js',
                     '/public/js/datepicker/datepicker.min.js',
                     '/public/js/datepicker/datepicker.zh.js',
+                    '/public/js/PinYinOrder.bundle.js',
                     '/public/js/zh_calc.js',
                     '/public/js/path_tree.js',
                     '/public/js/contract_detail.js',
@@ -2660,6 +2661,49 @@ const JsFiles = {
                 ],
                 mergeFile: 'cost_stage_analysis',
             },
+            cost_compare: {
+                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',
+                    '/public/js/component/menu.js',
+                    '/public/js/moment/moment.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/shares/cs_tools.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/shares/ali_oss.js',
+                    '/public/js/shares/new_tag.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/shares/tools_att.js',
+                    '/public/js/shares/common_audit.js',
+                    '/public/js/cost_compare.js',
+                ],
+                mergeFile: 'cost_compare',
+            },
+            cost_gather: {
+                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',
+                    '/public/js/component/menu.js',
+                    '/public/js/moment/moment.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/cost_gather.js',
+                ],
+                mergeFile: 'cost_gather',
+            }
         },
     },
 };

+ 5 - 520
sql/update.sql

@@ -3,536 +3,18 @@
 -- 2. 表名前不可有库名!!!
 -- 3. 表结构部分,新添加的表,不可先创建后修改!!!
 --
+-- Version todo
+-- uat todo
+-- prod todo
 
 ------------------------------------
 -- 表结构
 ------------------------------------
 
-ALTER TABLE `zh_contract`
-ADD COLUMN `need_shenpi` tinyint(1) NULL DEFAULT 0 COMMENT '是否需要审批' AFTER `name`,
-ADD COLUMN `status` tinyint(2) NULL DEFAULT 1 COMMENT '审批状态' AFTER `need_shenpi`,
-ADD COLUMN `times` tinyint(3) NULL DEFAULT 1 COMMENT '审批次数' AFTER `status`,
-ADD COLUMN `sp_group` int(11) NULL DEFAULT 0 COMMENT '固定审批组id' AFTER `times`,
-ADD COLUMN `final_auditor_str` varchar(50) NOT NULL DEFAULT '' COMMENT '终审人相关(cache)' AFTER `sp_group`,
-ADD COLUMN `type` varchar(255) NULL DEFAULT '' COMMENT '合同类型(筛选的字段)' AFTER `final_auditor_str`,
-ADD COLUMN `remark1` varchar(1000) NULL DEFAULT '' COMMENT '备注1' AFTER `remark`,
-ADD COLUMN `tax` tinyint(3) NULL COMMENT '税率' AFTER `calc2`,
-ADD COLUMN `attribute_json` mediumtext NULL COMMENT '附加属性json' AFTER `tax`;
-
-ALTER TABLE `zh_contract_pay`
-ADD COLUMN `need_shenpi` tinyint(1) NULL COMMENT '是否需要审批' AFTER `fpcid`,
-ADD COLUMN `status` tinyint(2) NULL DEFAULT 1 COMMENT '审批状态' AFTER `need_shenpi`,
-ADD COLUMN `times` tinyint(3) NULL DEFAULT 1 COMMENT '审批次数' AFTER `status`,
-ADD COLUMN `sp_group` int(11) NULL DEFAULT 0 COMMENT '固定审批组id' AFTER `times`,
-ADD COLUMN `used` varchar(255) NULL DEFAULT '合同' COMMENT '资金用途' AFTER `pay_time`,
-ADD COLUMN `final_auditor_str` varchar(50) NOT NULL DEFAULT '' COMMENT '终审人相关(cache)' AFTER `sp_group`;
-
-ALTER TABLE `zh_contract_tree`
-ADD COLUMN `remark1` varchar(1000) NULL DEFAULT '' COMMENT '备注1' AFTER `remark`;
-
-ALTER TABLE `zh_shenpi_audit`
-ADD COLUMN `audit_group` varchar(20) NOT NULL DEFAULT '' COMMENT '审批分组' AFTER `audit_ledger_id`,
-ADD COLUMN `audit_group_order` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组内审批顺序' AFTER `audit_group`,
-ADD COLUMN `audit_group_limit` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组内是否受限' AFTER `audit_group_order`,
-ADD COLUMN `audit_checkno_valid` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否允许审批退回' AFTER `audit_group_limit`,
-ADD COLUMN `audit_group_need` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '组内最少审批人数' AFTER `audit_checkno_valid`,
-ADD COLUMN `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间' AFTER `sp_group`;
-
-ALTER TABLE `zh_stage_audit`
-ADD COLUMN `audit_group` varchar(20) NOT NULL DEFAULT '' COMMENT '审批分组' AFTER `audit_ledger_id`,
-ADD COLUMN `audit_group_order` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组内顺序' AFTER `audit_group`,
-ADD COLUMN `audit_group_limit` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组内是否受限' AFTER `audit_group_order`,
-ADD COLUMN `audit_checkno_valid` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否允许审批退回' AFTER `audit_group_limit`,
-ADD COLUMN `audit_group_need` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '组内最少审批人数' AFTER `audit_checkno_valid`;
-
-ALTER TABLE `zh_tender_permission`
-ADD COLUMN `cost` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成本管理权限(,分隔,具体见代码定义)' AFTER `schedule`;
-
-ALTER TABLE `zh_bills_template_list`
-ADD COLUMN `sub_type` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '二级分类' AFTER `remark`;
-
 ALTER TABLE `zh_sub_project`
-ADD COLUMN `cost_ledger_template` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '成本台账模板id' AFTER `common_json`,
-ADD COLUMN `cost_analysis_template` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '成本分析模板id' AFTER `cost_ledger_template`,
-ADD COLUMN `cost_calc_template` varchar(36) NOT NULL DEFAULT '' COMMENT '成本分析,计算模板id' AFTER `cost_analysis_template`;
-
-CREATE TABLE `zh_cost_stage`  (
-  `id` varchar(36) NOT NULL COMMENT 'uuid',
-  `tid` int(11) UNSIGNED NOT NULL COMMENT '标段id(zh_tender.id)',
-  `stage_type` varchar(20) NULL COMMENT '期类型(台账ledger/账面book/分析analysis)',
-  `stage_order` int(11) UNSIGNED NOT NULL COMMENT '期序号',
-  `rela_stage` json NULL COMMENT '关联期信息(json)--[{sid: varchar, sorder: int}]',
-  `create_user_id` int(11) 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 '最后修改时间',
-  `stage_date` varchar(20) NOT NULL COMMENT '报审月份',
-  `decimal` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '{"tp": 6}' COMMENT '小数位数',
-  `audit_times` tinyint(4) NOT NULL COMMENT '审批轮次',
-  `audit_status` tinyint(4) NOT NULL COMMENT '审批状态',
-  `audit_max_sort` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最大审批排序',
-  `audit_begin_time` timestamp NULL COMMENT '开始审批时间',
-  `audit_end_time` timestamp NULL COMMENT '审批结束时间',
-  `final_auditor_str` varchar(255) NOT NULL DEFAULT '' COMMENT '终审缓存信息',
-  `stage_tp` json NOT NULL COMMENT '金额',
-  `stage_pre_tp` json NOT NULL COMMENT '截止上期金额',
-  `calc_template` varchar(36) NOT NULL DEFAULT '' COMMENT '计算模板',
-  PRIMARY KEY (`id`),
-  INDEX `idx_tid_type`(`tid`, `stage_type`) USING BTREE
-);
-
-CREATE TABLE `zh_cost_stage_audit`  (
-  `id` bigint(20) NOT NULL AUTO_INCREMENT,
-  `tid` int(11) UNSIGNED NOT NULL COMMENT '标段id',
-  `stage_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
-  `stage_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '期类型',
-  `audit_id` int(11) UNSIGNED NOT NULL COMMENT '流程参与人id(含原报)',
-  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '参与人-姓名',
-  `company` varchar(60) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '参与人-单位',
-  `role` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '参与人-角色',
-  `mobile` varchar(15) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '参与人-电话',
-  `audit_times` int(11) UNSIGNED NOT NULL DEFAULT 1 COMMENT '审批次数',
-  `audit_order` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '预定流程顺序',
-  `audit_type` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '流程类型',
-  `active_order` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '实际流程顺序',
-  `audit_status` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '流程状态',
-  `audit_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '流程结束时间',
-  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
-  `opinion` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '意见',
-  PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
-
-CREATE TABLE `zh_cost_stage_file`  (
-  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
-  `tid` int(11) UNSIGNED NOT NULL COMMENT '标段id',
-  `stage_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '期stage_id(zh_cost_stage.id)',
-  `stage_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '类型(ledger/book/analysis/...)',
-  `rela_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid(zh_cost_stage_ledger.id/...)',
-  `rela_sub_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(zh_cost_stage_detail.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 '是否删除',
-  `extra_upload` 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_cost_stage_ledger` (
-  `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid',
-  `cost_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(不同期保持统一)',
-  `tender_id` int(11) unsigned NOT NULL COMMENT '标段id',
-  `stage_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
-  `tree_id` int(11) NOT NULL COMMENT '节点id',
-  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
-  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
-  `tree_order` mediumint(4) NOT NULL COMMENT '同级排序',
-  `tree_full_path` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path-tree_id',
-  `tree_is_leaf` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '是否叶子节点,界面显示辅助字段',
-  `code` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号',
-  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
-  `unit` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '单位',
-  `pre_pay_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '截止上期-付款',
-  `pre_cut_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '截止上期-扣款',
-  `pre_yf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '截止上期-应付',
-  `pre_sf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '截止上期-实付',
-  `pre_yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-应付不含税',
-  `pre_sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-实付不含税',
-  `tax` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '税率(%)',
-  `pay_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '付款',
-  `cut_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '扣款',
-  `yf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '应付',
-  `sf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '实付',
-  `yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '应付不含税',
-  `sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付不含税',
-  `postil` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '本期批注',
-  `memo` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
-  `add_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人',
-  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后编辑人',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
-  `read_pay_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期付款-只读',
-  `read_cut_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期扣款-只读',
-  `read_yf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期应付-只读',
-  `read_sf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期实付-只读',
-  `read_yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期应付不含税-只读',
-  `read_sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期实付不含税-只读',
-  `calc_his` json DEFAULT NULL COMMENT '本期历史',
-  `is_used` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否已使用',
-  `is_deal` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否为合同节点',
-  `had_detail` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '往期是否有明细数据',
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-
-CREATE TABLE `zh_cost_stage_detail`  (
-  `id` varchar(36) NOT NULL COMMENT 'uuid',
-  `tender_id` int(11) NOT NULL COMMENT '标段id',
-  `stage_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '期id',
-  `stage_order` int(11) NOT NULL COMMENT '期序号',
-  `ledger_id` varchar(36) NOT NULL COMMENT '所属台账id(zh_stage_ledger.id)',
-  `cost_id` varchar(36) CHARACTER SET utf16le NOT NULL COMMENT '所属台账cost_id(不同期一致zh_stage_ledger.cost_id)',
-  `source_cid` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '导入的合同id',
-  `d_order` int(11) NOT NULL COMMENT '排序',
-  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号',
-  `name` varchar(255) CHARACTER SET utf16 NOT NULL DEFAULT '' COMMENT '名称',
-  `party_b` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '乙方',
-  `pay_date` varchar(50) NOT NULL DEFAULT '' COMMENT '支付年月',
-  `pre_pay_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-付款',
-  `pre_cut_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-扣款',
-  `pre_yf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-应付',
-  `pre_sf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-实付',
-  `pre_yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-应付不含税',
-  `pre_sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-实付不含税',
-  `tax` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '税率(%)',
-  `pay_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '付款',
-  `cut_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '扣款',
-  `yf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '应付',
-  `sf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付',
-  `yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '应付不含税',
-  `sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付不含税',
-  `postil` varchar(1000) 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(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
-  `update_user_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后编辑人',
-  `update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '最后编辑时间',
-  `read_pay_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期付款-只读',
-  `read_cut_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期扣款-只读',
-  `read_yf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期应付-只读',
-  `read_sf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期实付-只读',
-  `read_yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期应付不含税-只读',
-  `read_sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期实付不含税-只读',
-  `calc_his` json NULL COMMENT '本期历史',
-  `is_used` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否已使用',
-  `is_deal` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否为合同节点',
-  PRIMARY KEY (`id`)
-);
-
-CREATE TABLE `zh_cost_stage_tag`  (
-  `id` varchar(36) NOT NULL COMMENT 'uuid',
-  `tender_id` int(11) NOT NULL COMMENT '标段id(zh_tender.id)',
-  `stage_id` varchar(11) NOT NULL COMMENT '期id(zh_cost_stage.id)',
-  `stage_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期类型(ledger/book/analysis/...)',
-  `rela_id` varchar(36) NOT NULL COMMENT '关联id(zh_cost_stage_ledger.id/...)',
-  `rela_sub_id` varchar(36) NOT NULL COMMENT '次关联id(zh_cost_stage_detail.id/...)',
-  `create_user_id` int(11) NOT NULL COMMENT '创建人id(zh_project_accnount.id)',
-  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_user_id` int(11) NOT NULL COMMENT '最后修改人id(zh_project_account.id)',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
-  `share` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否共享给其他参与人',
-  `color` varchar(7) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '书签颜色',
-  `comment` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL COMMENT '批注',
-  PRIMARY KEY (`id`)
-);
-
-CREATE TABLE `zh_cost_stage_book` (
-  `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(zh_cost_stage_ledger.id)',
-  `tender_id` int(11) unsigned NOT NULL COMMENT '标段id',
-  `stage_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
-  `ledger_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '关联台账id(zh_stage_ledger.id)',
-  `cost_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '关联台账cost_id(不同期一致zh_stage_ledger.cost_id)',
-  `pre_in_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '截止上期-入账金额',
-  `pre_in_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '截止上期-入账金额不含税',
-  `in_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '入账金额',
-  `in_excl_tax_tp` decimal(24, 8) NOT NULL COMMENT '入账金额不含税',
-  `postil` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '本期批注',
-  `memo` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
-  `add_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人',
-  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后编辑人',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
-  `read_in_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期入账-只读',
-  `read_in_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '本期入账不含税-只读',
-  `calc_his` json DEFAULT NULL COMMENT '本期历史',
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-
-CREATE TABLE `zh_cost_stage_book_detail` (
-  `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(zh_cost_stage_ledger.id)',
-  `tender_id` int(11) unsigned NOT NULL COMMENT '标段id',
-  `stage_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL COMMENT '期id',
-  `ledger_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '关联台账id(zh_stage_detail.ledger_id)',
-  `detail_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '关联明细id(zh_stage_detail.id)',
-  `in_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '入账金额',
-  `in_excl_tax_tp` decimal(24, 8) NOT NULL COMMENT '入账金额不含税',
-  `postil` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '本期批注',
-  `memo` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
-  `add_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人',
-  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后编辑人',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
-  `read_in_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期入账-只读',
-  `read_in_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '本期入账不含税-只读',
-  `calc_his` json DEFAULT NULL COMMENT '本期历史',
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-
-CREATE TABLE `zh_cost_stage_analysis`  (
-  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid',
-  `cost_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(不同期保持统一)',
-  `tender_id` int(11) UNSIGNED NOT NULL COMMENT '标段id',
-  `stage_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
-  `tree_id` int(11) NOT NULL COMMENT '节点id',
-  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
-  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
-  `tree_order` mediumint(4) NOT NULL COMMENT '同级排序',
-  `tree_full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path-tree_id',
-  `tree_is_leaf` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否叶子节点,界面显示辅助字段',
-  `node_type` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 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 '名称',
-  `unit` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '单位',
-  `tax` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '税率(%)',
-  `yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '应付金额-不含税',
-  `in_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '入账金额-不含税',
-  `sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付金额-不含税',
-  `sf_percent` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付比例',
-  `postil` varchar(1000) NOT NULL DEFAULT '' COMMENT '本期批注',
-  `str1` varchar(255) NOT NULL DEFAULT '' COMMENT '文本',
-  `str2` varchar(255) NOT NULL DEFAULT '' COMMENT '文本',
-  `str3` varchar(255) NOT NULL DEFAULT '' COMMENT '文本',
-  `str4` varchar(255) NOT NULL DEFAULT '' COMMENT '文本',
-  `num_a` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_b` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_c` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_d` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_e` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_f` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_g` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_h` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_i` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_j` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_k` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_l` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_m` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_n` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_o` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `num_p` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
-  `calc_read` json DEFAULT NULL COMMENT '本期计算-只读',
-  `calc_his` json DEFAULT NULL COMMENT '本期历史',
-  `add_user_id` int(11) NOT NULL COMMENT '新增人id',
-  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '新增时间',
-  `update_user_id` int(11) NOT NULL COMMENT '最后更新人id',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
-  `calc_type` int(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '计算类型(收入1,支出2,其他0)',
-  PRIMARY KEY (`id`)
-);
+ADD COLUMN `cost_ledger_col_set` json NULL COMMENT '成本报审-列设置' AFTER `cost_calc_template`;
 
-CREATE TABLE `zh_cost_stage_analysis_detail`  (
-  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
-  `tender_id` int(11) NOT NULL COMMENT '标段id',
-  `stage_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '期id',
-  `ledger_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '所属台账id(zh_stage_ledger.id)',
-  `cost_id` varchar(36) CHARACTER SET utf16le COLLATE utf16le_general_ci NOT NULL COMMENT '所属台账cost_id(不同期一致zh_stage_ledger.cost_id)',
-  `source_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '导入的合同id/明细id',
-  `d_order` int(11) NOT NULL COMMENT '排序',
-  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号',
-  `name` varchar(255) CHARACTER SET utf16 COLLATE utf16_general_ci NOT NULL DEFAULT '' COMMENT '名称',
-  `party_b` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '乙方',
-  `tax` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '税率(%)',
-  `yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '应付金额-不含税',
-  `in_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '入账金额-不含税',
-  `sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付金额-不含税',
-  `sf_percent` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付比例',
-  `postil` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '本期批注',
-  `str1` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '文本',
-  `str2` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '文本',
-  `str3` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '文本',
-  `str4` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '文本',
-  `num_a` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_b` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_c` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_d` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_e` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_f` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_g` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_h` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_i` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_j` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_k` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_l` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_m` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_n` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_o` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `num_p` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
-  `calc_read` json NULL COMMENT '本期计算-只读',
-  `calc_his` json NULL COMMENT '本期计算-历史',
-  `add_user_id` int(11) NOT NULL COMMENT '新增人id',
-  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '新增时间',
-  `update_user_id` int(11) NOT NULL COMMENT '最后更新人id',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
-  `is_deal` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否为合同',
-  PRIMARY KEY (`id`)
-);
-
-CREATE TABLE `zh_calc_tmpl_folder`  (
-  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
-  `pid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '项目id(zh_project.id)',
-  `type` varchar(20) NOT NULL COMMENT '模板类型(posCalc/...)',
-  `master_id` varchar(36) NOT NULL COMMENT 'pid-type',
-  `name` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
-  `tree_id` int(11) NOT NULL COMMENT '节点id',
-  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
-  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
-  `tree_order` mediumint(4) NOT NULL COMMENT '同级排序',
-  `tree_full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path-tree_id',
-  `tree_is_leaf` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否叶子节点,界面显示辅助字段',
-  `add_user_id` int(11) NOT NULL COMMENT '新增人id',
-  `add_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '新增时间',
-  `update_user_id` int(11) NOT NULL COMMENT '最后更新人id',
-  `update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '最后更新时间',
-  PRIMARY KEY (`id`)
-);
-
-ALTER TABLE `zh_pos_calc_detail`
-ADD COLUMN `num_j` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值1' AFTER `num_i`,
-ADD COLUMN `num_k` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值2' AFTER `num_j`,
-ADD COLUMN `num_l` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值3' AFTER `num_k`,
-ADD COLUMN `num_m` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值4' AFTER `num_l`,
-ADD COLUMN `num_n` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值5' AFTER `num_m`,
-ADD COLUMN `num_o` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值6' AFTER `num_n`,
-ADD COLUMN `num_p` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值7' AFTER `num_o`,
-ADD COLUMN `num_q` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值8' AFTER `num_p`,
-ADD COLUMN `num_r` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值9' AFTER `num_q`,
-ADD COLUMN `num_s` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值5' AFTER `num_r`,
-ADD COLUMN `num_t` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值6' AFTER `num_s`,
-ADD COLUMN `num_u` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值7' AFTER `num_t`;
-
-ALTER TABLE `zh_calc_tmpl`
-ADD COLUMN `folder_id` varchar(36) NOT NULL DEFAULT '' COMMENT '分类id(zh_calc_tmpl_folder.id)' AFTER `tid`;
-
-ALTER TABLE `calculation`.`zh_project`
-ADD COLUMN `calc_tmpl_set` json NULL COMMENT '计算模板配置' AFTER `common_json`;
-
-CREATE TABLE `zh_budget_ctrl`  (
-  `id` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '自增id',
-  `bid` int(10) NOT NULL COMMENT '概算投资项目id',
-  `tree_id` int(10) NOT NULL COMMENT '节点id',
-  `tree_pid` int(10) NOT NULL COMMENT '父节点id',
-  `level` tinyint(4) NOT NULL COMMENT '层级',
-  `order` mediumint(4) NOT NULL DEFAULT 0 COMMENT '同级排序',
-  `full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '层级定位辅助字段parent.full_path.ledger_id',
-  `is_leaf` tinyint(1) NOT NULL COMMENT '是否叶子节点,界面显示辅助字段',
-  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '节点编号',
-  `b_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
-  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称',
-  `unit` varchar(15) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '单位',
-  `unit_price` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '单价',
-  `quantity` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数量',
-  `total_price` decimal(24, 8) NULL DEFAULT 0.00000000 COMMENT '金额',
-  `dgn_qty1` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '设计数量1',
-  `dgn_qty2` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '设计数量2',
-  `drawing_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图册号',
-  `memo` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '备注',
-  `node_type` int(4) UNSIGNED NULL DEFAULT 0 COMMENT '节点类别',
-  `source` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '添加源',
-  `remark` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '备注',
-  PRIMARY KEY (`id`) USING BTREE,
-  INDEX `idx_tender_id`(`bid`) USING BTREE,
-  INDEX `idx_template_pid`(`tree_pid`) USING BTREE,
-  INDEX `idx_level`(`level`) USING BTREE,
-  INDEX `idx_full_path`(`bid`, `full_path`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '控制目标数据' ROW_FORMAT = Dynamic;
-
-ALTER TABLE `zh_budget_final`
-ADD COLUMN `ctrl_dgn_qty1` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '控制目标-设计数量1' AFTER `zb_tp`,
-ADD COLUMN `ctrl_dgn_qty2` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '控制目标-设计数量1' AFTER `ctrl_dgn_qty1`,
-ADD COLUMN `ctrl_dgn_qty` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '控制目标-设计数量1/设计数量2' AFTER `ctrl_dgn_qty2`,
-ADD COLUMN `ctrl_dgn_price` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '控制目标-经济指标' AFTER `ctrl_dgn_qty`,
-ADD COLUMN `ctrl_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '控制目标-金额' AFTER `ctrl_dgn_price`;
-
-ALTER TABLE `zh_budget_std`
-ADD COLUMN `ctrl_chapter_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '控制目标-项目节-id列表(\',\'分隔)' AFTER `zb_bills_id`,
-ADD COLUMN `ctrl_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '控制目标-新建模板-id列表(\',\'分隔)' AFTER `ctrl_chapter_id`,
-ADD COLUMN `ctrl_bills_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '控制目标-工程量清单-id列表(\',\'分隔)' AFTER `ctrl_template_id`;
-
-ALTER TABLE `zh_sub_project` ADD `shenpi` VARCHAR(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '审批流程设置' AFTER `payment_setting`;
-
-ALTER TABLE `zh_shenpi_group`
-ADD COLUMN `spid` varchar(255) NULL DEFAULT NULL COMMENT '项目id' AFTER `id`;
-
-ALTER TABLE `zh_shenpi_audit`
-ADD COLUMN `spid` varchar(255) NULL DEFAULT NULL COMMENT '项目id' AFTER `id`,
-MODIFY COLUMN `tid` int(11) NULL DEFAULT NULL COMMENT '标段id' AFTER `spid`;
-
-CREATE TABLE `zh_contract_sp_audit` (
-  `id` int NOT NULL AUTO_INCREMENT,
-  `spid` varchar(255) NULL DEFAULT NULL COMMENT '项目id',
-  `tid` int NULL DEFAULT NULL COMMENT '标段id',
-  `cid` varchar(100) NULL DEFAULT NULL COMMENT '合同详情id',
-  `cpid` int NULL DEFAULT NULL COMMENT '合同支付id',
-  `aid` int NOT NULL COMMENT '审批人id',
-  `order` int NOT NULL COMMENT '审批顺序',
-  `times` int NOT NULL COMMENT '审批次数',
-  `status` tinyint(1) NOT NULL COMMENT '审批状态',
-  `begin_time` datetime NULL DEFAULT NULL COMMENT '开始审批时间',
-  `end_time` datetime NULL DEFAULT NULL COMMENT '结束审批时间',
-  `opinion` varchar(1000) NULL DEFAULT NULL COMMENT '审批意见',
-  `audit_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '审批类型(1个人,2会签,3或签)',
-  `audit_order` tinyint(4) NOT NULL DEFAULT 0 COMMENT '审批顺序',
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT = '合同详情和合同支付,回款审批人表';
-
-ALTER TABLE `zh_ancillary_gcl`
-ADD COLUMN `pid` varchar(36) NOT NULL DEFAULT '' COMMENT '计量单元id' AFTER `lid`,
-ADD COLUMN `calc_template` varchar(36) NOT NULL DEFAULT '' COMMENT '明细计算模板' AFTER `update_time`;
-
-CREATE TABLE `zh_ancillary_gcl_detail`  (
-  `id` varchar(36) NOT NULL COMMENT 'uuid',
-  `tid` int(11) NOT NULL COMMENT '标段id(zh_tender.id)',
-  `lid` varchar(36) NOT NULL COMMENT '台账id(zh_ledger.id)',
-  `pid` varchar(36) NOT NULL COMMENT '计量单元id(zh_pos.id)',
-  `ag_id` varchar(36) NOT NULL COMMENT '附属工程量id(zh_ancillary_gcl.id)',
-  `agd_order` int(11) NOT NULL COMMENT '排序',
-  `create_user_id` int(11) NOT NULL COMMENT '新增用户id(zh_project_account.id)',
-  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '新增时间',
-  `update_user_id` int(11) NOT NULL COMMENT '最后修改用户id(zh_project_account.id)',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
-  `qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数量',
-  `expr` varchar(255) NOT NULL DEFAULT '' COMMENT '数量计算式',
-  `str1` varchar(255) NOT NULL DEFAULT '' COMMENT '文本1',
-  `str2` varchar(255) NOT NULL DEFAULT '' COMMENT '文本2',
-  `str3` varchar(255) NOT NULL DEFAULT '' COMMENT '文本3',
-  `str4` varchar(255) NOT NULL DEFAULT '' COMMENT '文本4',
-  `spec` varchar(50) NOT NULL DEFAULT '' COMMENT '规格',
-  `num_a` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值a',
-  `num_b` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值b',
-  `num_c` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值c',
-  `num_d` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值d',
-  `num_e` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值e',
-  `num_f` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值f',
-  `num_g` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值g',
-  `num_h` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值h',
-  `num_i` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值i',
-  `num_j` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值j',
-  `num_k` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值k',
-  `num_l` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值l',
-  `num_m` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值m',
-  `num_n` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值n',
-  `num_o` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值o',
-  `num_p` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值p',
-  `num_q` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值q',
-  `num_r` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值r',
-  `num_s` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值s',
-  `num_t` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值t',
-  `num_u` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值u',
-  PRIMARY KEY (`id`)
-);
-
-ALTER TABLE `zh_tender_cache`
-MODIFY COLUMN `stage_flow_cur_info` varchar(5000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期-当前流程信息(json)' AFTER `stage_flow_cur_uid`,
-MODIFY COLUMN `stage_flow_pre_info` varchar(5000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期-上一流程信息(json)' AFTER `stage_flow_pre_uid`;
 
 ------------------------------------
 -- 表数据
-------------------------------------
+------------------------------------

+ 1 - 1
sql/update20260403.sql

@@ -348,7 +348,7 @@ CREATE TABLE `zh_std_extra`  (
   `extra_pid` int(8) NOT NULL COMMENT '父节点id',
   `level` tinyint(2) NOT NULL DEFAULT 1 COMMENT '目录层级',
   `order` tinyint(5) NOT NULL,
-  `full_path` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+  `full_path` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
   `is_leaf` tinyint(1) NOT NULL,
   `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '分类名称',
   `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号',

+ 592 - 0
sql/update20260527.sql

@@ -0,0 +1,592 @@
+-- sql提交须知:
+-- 1. 请按如下分类提交sql!!!
+-- 2. 表名前不可有库名!!!
+-- 3. 表结构部分,新添加的表,不可先创建后修改!!!
+--
+-- Version V3.5.53.0553
+-- uat 2026-05-26
+-- prod 2026-06-27
+
+------------------------------------
+-- 表结构
+------------------------------------
+
+ALTER TABLE `zh_contract`
+ADD COLUMN `supplement_price` decimal(30, 8) NULL DEFAULT 0 COMMENT '补充合同金额' AFTER `total_price`,
+ADD COLUMN `need_shenpi` tinyint(1) NULL DEFAULT 0 COMMENT '是否需要审批' AFTER `name`,
+ADD COLUMN `status` tinyint(2) NULL DEFAULT 1 COMMENT '审批状态' AFTER `need_shenpi`,
+ADD COLUMN `times` tinyint(3) NULL DEFAULT 1 COMMENT '审批次数' AFTER `status`,
+ADD COLUMN `sp_group` int(11) NULL DEFAULT 0 COMMENT '固定审批组id' AFTER `times`,
+ADD COLUMN `final_auditor_str` varchar(50) NOT NULL DEFAULT '' COMMENT '终审人相关(cache)' AFTER `sp_group`,
+ADD COLUMN `type` varchar(255) NULL DEFAULT '' COMMENT '合同类型(筛选的字段)' AFTER `final_auditor_str`,
+ADD COLUMN `remark1` varchar(1000) NULL DEFAULT '' COMMENT '备注1' AFTER `remark`,
+ADD COLUMN `tax` tinyint(3) NULL COMMENT '税率' AFTER `calc2`,
+ADD COLUMN `attribute_json` mediumtext NULL COMMENT '附加属性json' AFTER `tax`,
+ADD COLUMN `exist_supplement` tinyint(1) NULL DEFAULT 0 COMMENT '是否存在补充合同' AFTER `exist_pay`;
+
+ALTER TABLE `zh_contract_pay`
+ADD COLUMN `need_shenpi` tinyint(1) NULL COMMENT '是否需要审批' AFTER `fpcid`,
+ADD COLUMN `status` tinyint(2) NULL DEFAULT 1 COMMENT '审批状态' AFTER `need_shenpi`,
+ADD COLUMN `times` tinyint(3) NULL DEFAULT 1 COMMENT '审批次数' AFTER `status`,
+ADD COLUMN `sp_group` int(11) NULL DEFAULT 0 COMMENT '固定审批组id' AFTER `times`,
+ADD COLUMN `used` varchar(255) NULL DEFAULT '合同' COMMENT '资金用途' AFTER `pay_time`,
+ADD COLUMN `final_auditor_str` varchar(50) NOT NULL DEFAULT '' COMMENT '终审人相关(cache)' AFTER `sp_group`;
+
+ALTER TABLE `zh_contract_tree`
+ADD COLUMN `remark1` varchar(1000) NULL DEFAULT '' COMMENT '备注1' AFTER `remark`;
+
+ALTER TABLE `zh_shenpi_audit`
+ADD COLUMN `audit_group` varchar(20) NOT NULL DEFAULT '' COMMENT '审批分组' AFTER `audit_ledger_id`,
+ADD COLUMN `audit_group_order` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组内审批顺序' AFTER `audit_group`,
+ADD COLUMN `audit_group_limit` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组内是否受限' AFTER `audit_group_order`,
+ADD COLUMN `audit_checkno_valid` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否允许审批退回' AFTER `audit_group_limit`,
+ADD COLUMN `audit_group_need` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '组内最少审批人数' AFTER `audit_checkno_valid`,
+ADD COLUMN `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间' AFTER `sp_group`;
+
+ALTER TABLE `zh_stage_audit`
+ADD COLUMN `audit_group` varchar(20) NOT NULL DEFAULT '' COMMENT '审批分组' AFTER `audit_ledger_id`,
+ADD COLUMN `audit_group_order` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组内顺序' AFTER `audit_group`,
+ADD COLUMN `audit_group_limit` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组内是否受限' AFTER `audit_group_order`,
+ADD COLUMN `audit_checkno_valid` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否允许审批退回' AFTER `audit_group_limit`,
+ADD COLUMN `audit_group_need` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '组内最少审批人数' AFTER `audit_checkno_valid`;
+
+ALTER TABLE `zh_tender_permission`
+ADD COLUMN `cost` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成本管理权限(,分隔,具体见代码定义)' AFTER `schedule`;
+
+ALTER TABLE `zh_bills_template_list`
+ADD COLUMN `sub_type` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '二级分类' AFTER `remark`;
+
+ALTER TABLE `zh_sub_project`
+ADD COLUMN `cost_ledger_template` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '成本台账模板id' AFTER `common_json`,
+ADD COLUMN `cost_analysis_template` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '成本分析模板id' AFTER `cost_ledger_template`,
+ADD COLUMN `cost_calc_template` varchar(36) NOT NULL DEFAULT '' COMMENT '成本分析,计算模板id' AFTER `cost_analysis_template`;
+
+CREATE TABLE `zh_cost_stage`  (
+  `id` varchar(36) NOT NULL COMMENT 'uuid',
+  `tid` int(11) UNSIGNED NOT NULL COMMENT '标段id(zh_tender.id)',
+  `stage_type` varchar(20) NULL COMMENT '期类型(台账ledger/账面book/分析analysis)',
+  `stage_order` int(11) UNSIGNED NOT NULL COMMENT '期序号',
+  `rela_stage` json NULL COMMENT '关联期信息(json)--[{sid: varchar, sorder: int}]',
+  `create_user_id` int(11) 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 '最后修改时间',
+  `stage_date` varchar(20) NOT NULL COMMENT '报审月份',
+  `decimal` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '{"tp": 6}' COMMENT '小数位数',
+  `audit_times` tinyint(4) NOT NULL COMMENT '审批轮次',
+  `audit_status` tinyint(4) NOT NULL COMMENT '审批状态',
+  `audit_max_sort` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最大审批排序',
+  `audit_begin_time` timestamp NULL COMMENT '开始审批时间',
+  `audit_end_time` timestamp NULL COMMENT '审批结束时间',
+  `final_auditor_str` varchar(255) NOT NULL DEFAULT '' COMMENT '终审缓存信息',
+  `stage_tp` json NULL COMMENT '金额',
+  `stage_pre_tp` json NULL COMMENT '截止上期金额',
+  `calc_template` varchar(36) NOT NULL DEFAULT '' COMMENT '计算模板',
+  PRIMARY KEY (`id`),
+  INDEX `idx_tid_type`(`tid`, `stage_type`) USING BTREE
+);
+
+CREATE TABLE `zh_cost_stage_audit`  (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) UNSIGNED NOT NULL COMMENT '标段id',
+  `stage_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
+  `stage_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '期类型',
+  `audit_id` int(11) UNSIGNED NOT NULL COMMENT '流程参与人id(含原报)',
+  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '参与人-姓名',
+  `company` varchar(60) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '参与人-单位',
+  `role` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '参与人-角色',
+  `mobile` varchar(15) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '参与人-电话',
+  `audit_times` int(11) UNSIGNED NOT NULL DEFAULT 1 COMMENT '审批次数',
+  `audit_order` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '预定流程顺序',
+  `audit_type` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '流程类型',
+  `active_order` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '实际流程顺序',
+  `audit_status` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '流程状态',
+  `audit_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '流程结束时间',
+  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
+  `opinion` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '意见',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
+
+CREATE TABLE `zh_cost_stage_file`  (
+  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `tid` int(11) UNSIGNED NOT NULL COMMENT '标段id',
+  `stage_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '期stage_id(zh_cost_stage.id)',
+  `stage_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '类型(ledger/book/analysis/...)',
+  `rela_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid(zh_cost_stage_ledger.id/...)',
+  `rela_sub_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(zh_cost_stage_detail.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 '是否删除',
+  `extra_upload` 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_cost_stage_ledger` (
+  `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid',
+  `cost_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(不同期保持统一)',
+  `tender_id` int(11) unsigned NOT NULL COMMENT '标段id',
+  `stage_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
+  `tree_id` int(11) NOT NULL COMMENT '节点id',
+  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
+  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
+  `tree_order` mediumint(4) NOT NULL COMMENT '同级排序',
+  `tree_full_path` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path-tree_id',
+  `tree_is_leaf` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '是否叶子节点,界面显示辅助字段',
+  `code` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号',
+  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
+  `unit` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '单位',
+  `pre_pay_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '截止上期-付款',
+  `pre_cut_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '截止上期-扣款',
+  `pre_yf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '截止上期-应付',
+  `pre_sf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '截止上期-实付',
+  `pre_yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-应付不含税',
+  `pre_sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-实付不含税',
+  `tax` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '税率(%)',
+  `pay_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '付款',
+  `cut_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '扣款',
+  `yf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '应付',
+  `sf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '实付',
+  `yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '应付不含税',
+  `sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付不含税',
+  `postil` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '本期批注',
+  `memo` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
+  `add_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人',
+  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后编辑人',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
+  `read_pay_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期付款-只读',
+  `read_cut_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期扣款-只读',
+  `read_yf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期应付-只读',
+  `read_sf_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期实付-只读',
+  `read_yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期应付不含税-只读',
+  `read_sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期实付不含税-只读',
+  `calc_his` json DEFAULT NULL COMMENT '本期历史',
+  `is_used` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否已使用',
+  `is_deal` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否为合同节点',
+  `had_detail` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '往期是否有明细数据',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+CREATE TABLE `zh_cost_stage_detail`  (
+  `id` varchar(36) NOT NULL COMMENT 'uuid',
+  `tender_id` int(11) NOT NULL COMMENT '标段id',
+  `stage_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '期id',
+  `stage_order` int(11) NOT NULL COMMENT '期序号',
+  `ledger_id` varchar(36) NOT NULL COMMENT '所属台账id(zh_stage_ledger.id)',
+  `cost_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '所属台账cost_id(不同期一致zh_stage_ledger.cost_id)',
+  `source_cid` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '导入的合同id',
+  `d_order` int(11) NOT NULL COMMENT '排序',
+  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号',
+  `name` varchar(255) CHARACTER SET utf16 NOT NULL DEFAULT '' COMMENT '名称',
+  `party_b` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '乙方',
+  `pay_date` varchar(50) NOT NULL DEFAULT '' COMMENT '支付年月',
+  `pre_pay_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-付款',
+  `pre_cut_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-扣款',
+  `pre_yf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-应付',
+  `pre_sf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-实付',
+  `pre_yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-应付不含税',
+  `pre_sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '截止上期-实付不含税',
+  `tax` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '税率(%)',
+  `pay_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '付款',
+  `cut_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '扣款',
+  `yf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '应付',
+  `sf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付',
+  `yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '应付不含税',
+  `sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付不含税',
+  `postil` varchar(1000) 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(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
+  `update_user_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后编辑人',
+  `update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '最后编辑时间',
+  `read_pay_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期付款-只读',
+  `read_cut_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期扣款-只读',
+  `read_yf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期应付-只读',
+  `read_sf_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期实付-只读',
+  `read_yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期应付不含税-只读',
+  `read_sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '本期实付不含税-只读',
+  `calc_his` json NULL COMMENT '本期历史',
+  `is_used` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否已使用',
+  `is_deal` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否为合同节点',
+  PRIMARY KEY (`id`)
+);
+
+CREATE TABLE `zh_cost_stage_tag`  (
+  `id` varchar(36) NOT NULL COMMENT 'uuid',
+  `tender_id` int(11) NOT NULL COMMENT '标段id(zh_tender.id)',
+  `stage_id` varchar(11) NOT NULL COMMENT '期id(zh_cost_stage.id)',
+  `stage_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期类型(ledger/book/analysis/...)',
+  `rela_id` varchar(36) NOT NULL COMMENT '关联id(zh_cost_stage_ledger.id/...)',
+  `rela_sub_id` varchar(36) NOT NULL COMMENT '次关联id(zh_cost_stage_detail.id/...)',
+  `create_user_id` int(11) NOT NULL COMMENT '创建人id(zh_project_accnount.id)',
+  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_user_id` int(11) NOT NULL COMMENT '最后修改人id(zh_project_account.id)',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
+  `share` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否共享给其他参与人',
+  `color` varchar(7) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '书签颜色',
+  `comment` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL COMMENT '批注',
+  PRIMARY KEY (`id`)
+);
+
+CREATE TABLE `zh_cost_stage_book` (
+  `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(zh_cost_stage_ledger.id)',
+  `tender_id` int(11) unsigned NOT NULL COMMENT '标段id',
+  `stage_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
+  `ledger_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '关联台账id(zh_stage_ledger.id)',
+  `cost_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '关联台账cost_id(不同期一致zh_stage_ledger.cost_id)',
+  `pre_in_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '截止上期-入账金额',
+  `pre_in_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '截止上期-入账金额不含税',
+  `in_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '入账金额',
+  `in_excl_tax_tp` decimal(24, 8) NOT NULL COMMENT '入账金额不含税',
+  `postil` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '本期批注',
+  `memo` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
+  `add_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人',
+  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后编辑人',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
+  `read_in_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期入账-只读',
+  `read_in_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '本期入账不含税-只读',
+  `calc_his` json DEFAULT NULL COMMENT '本期历史',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+CREATE TABLE `zh_cost_stage_book_detail` (
+  `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(zh_cost_stage_ledger.id)',
+  `tender_id` int(11) unsigned NOT NULL COMMENT '标段id',
+  `stage_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL COMMENT '期id',
+  `stage_order` int(11) NOT NULL COMMENT '期序号',
+  `ledger_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '关联台账id(zh_stage_detail.ledger_id)',
+  `detail_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '关联明细id(zh_stage_detail.id)',
+  `in_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '入账金额',
+  `in_excl_tax_tp` decimal(24, 8) NOT NULL COMMENT '入账金额不含税',
+  `postil` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '本期批注',
+  `memo` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
+  `add_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人',
+  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后编辑人',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
+  `read_in_tp` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '本期入账-只读',
+  `read_in_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '本期入账不含税-只读',
+  `calc_his` json DEFAULT NULL COMMENT '本期历史',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+CREATE TABLE `zh_cost_stage_analysis`  (
+  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid',
+  `cost_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(不同期保持统一)',
+  `tender_id` int(11) UNSIGNED NOT NULL COMMENT '标段id',
+  `stage_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
+  `tree_id` int(11) NOT NULL COMMENT '节点id',
+  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
+  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
+  `tree_order` mediumint(4) NOT NULL COMMENT '同级排序',
+  `tree_full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path-tree_id',
+  `tree_is_leaf` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否叶子节点,界面显示辅助字段',
+  `node_type` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 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 '名称',
+  `unit` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '单位',
+  `tax` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '税率(%)',
+  `yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '应付金额-不含税',
+  `in_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '入账金额-不含税',
+  `sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付金额-不含税',
+  `sf_percent` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付比例',
+  `postil` varchar(1000) NOT NULL DEFAULT '' COMMENT '本期批注',
+  `str1` varchar(255) NOT NULL DEFAULT '' COMMENT '文本',
+  `str2` varchar(255) NOT NULL DEFAULT '' COMMENT '文本',
+  `str3` varchar(255) NOT NULL DEFAULT '' COMMENT '文本',
+  `str4` varchar(255) NOT NULL DEFAULT '' COMMENT '文本',
+  `num_a` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_b` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_c` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_d` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_e` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_f` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_g` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_h` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_i` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_j` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_k` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_l` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_m` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_n` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_o` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `num_p` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值',
+  `calc_read` json DEFAULT NULL COMMENT '本期计算-只读',
+  `calc_his` json DEFAULT NULL COMMENT '本期历史',
+  `add_user_id` int(11) NOT NULL COMMENT '新增人id',
+  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '新增时间',
+  `update_user_id` int(11) NOT NULL COMMENT '最后更新人id',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
+  `calc_type` int(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '计算类型(收入1,支出2,其他0)',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+CREATE TABLE `zh_cost_stage_analysis_detail`  (
+  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `tender_id` int(11) NOT NULL COMMENT '标段id',
+  `stage_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '期id',
+  `ledger_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '所属台账id(zh_stage_ledger.id)',
+  `cost_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '所属台账cost_id(不同期一致zh_stage_ledger.cost_id)',
+  `source_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '导入的合同id/明细id',
+  `d_order` int(11) NOT NULL COMMENT '排序',
+  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号',
+  `name` varchar(255) CHARACTER SET utf16 COLLATE utf16_general_ci NOT NULL DEFAULT '' COMMENT '名称',
+  `party_b` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '乙方',
+  `tax` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '税率(%)',
+  `yf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '应付金额-不含税',
+  `in_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '入账金额-不含税',
+  `sf_excl_tax_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付金额-不含税',
+  `sf_percent` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实付比例',
+  `postil` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '本期批注',
+  `str1` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '文本',
+  `str2` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '文本',
+  `str3` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '文本',
+  `str4` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '文本',
+  `num_a` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_b` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_c` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_d` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_e` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_f` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_g` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_h` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_i` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_j` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_k` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_l` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_m` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_n` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_o` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `num_p` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值',
+  `calc_read` json NULL COMMENT '本期计算-只读',
+  `calc_his` json NULL COMMENT '本期计算-历史',
+  `add_user_id` int(11) NOT NULL COMMENT '新增人id',
+  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '新增时间',
+  `update_user_id` int(11) NOT NULL COMMENT '最后更新人id',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
+  `is_deal` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否为合同',
+  PRIMARY KEY (`id`)
+);
+
+CREATE TABLE `zh_calc_tmpl_folder`  (
+  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `pid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '项目id(zh_project.id)',
+  `type` varchar(20) NOT NULL COMMENT '模板类型(posCalc/...)',
+  `master_id` varchar(36) NOT NULL COMMENT 'pid-type',
+  `name` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
+  `tree_id` int(11) NOT NULL COMMENT '节点id',
+  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
+  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
+  `tree_order` mediumint(4) NOT NULL COMMENT '同级排序',
+  `tree_full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path-tree_id',
+  `tree_is_leaf` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否叶子节点,界面显示辅助字段',
+  `add_user_id` int(11) NOT NULL COMMENT '新增人id',
+  `add_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '新增时间',
+  `update_user_id` int(11) NOT NULL COMMENT '最后更新人id',
+  `update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '最后更新时间',
+  PRIMARY KEY (`id`)
+);
+
+ALTER TABLE `zh_pos_calc_detail`
+ADD COLUMN `num_j` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值1' AFTER `num_i`,
+ADD COLUMN `num_k` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值2' AFTER `num_j`,
+ADD COLUMN `num_l` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值3' AFTER `num_k`,
+ADD COLUMN `num_m` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值4' AFTER `num_l`,
+ADD COLUMN `num_n` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值5' AFTER `num_m`,
+ADD COLUMN `num_o` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值6' AFTER `num_n`,
+ADD COLUMN `num_p` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值7' AFTER `num_o`,
+ADD COLUMN `num_q` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值8' AFTER `num_p`,
+ADD COLUMN `num_r` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值9' AFTER `num_q`,
+ADD COLUMN `num_s` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值5' AFTER `num_r`,
+ADD COLUMN `num_t` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值6' AFTER `num_s`,
+ADD COLUMN `num_u` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数值7' AFTER `num_t`;
+
+ALTER TABLE `zh_calc_tmpl`
+ADD COLUMN `folder_id` varchar(36) NOT NULL DEFAULT '' COMMENT '分类id(zh_calc_tmpl_folder.id)' AFTER `tid`;
+
+ALTER TABLE `calculation`.`zh_project`
+ADD COLUMN `calc_tmpl_set` json NULL COMMENT '计算模板配置' AFTER `common_json`;
+
+CREATE TABLE `zh_budget_ctrl`  (
+  `id` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '自增id',
+  `bid` int(10) NOT NULL COMMENT '概算投资项目id',
+  `tree_id` int(10) NOT NULL COMMENT '节点id',
+  `tree_pid` int(10) NOT NULL COMMENT '父节点id',
+  `level` tinyint(4) NOT NULL COMMENT '层级',
+  `order` mediumint(4) NOT NULL DEFAULT 0 COMMENT '同级排序',
+  `full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '层级定位辅助字段parent.full_path.ledger_id',
+  `is_leaf` tinyint(1) NOT NULL COMMENT '是否叶子节点,界面显示辅助字段',
+  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '节点编号',
+  `b_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
+  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '名称',
+  `unit` varchar(15) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '单位',
+  `unit_price` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '单价',
+  `quantity` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数量',
+  `total_price` decimal(24, 8) NULL DEFAULT 0.00000000 COMMENT '金额',
+  `dgn_qty1` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '设计数量1',
+  `dgn_qty2` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '设计数量2',
+  `drawing_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '图册号',
+  `memo` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '备注',
+  `node_type` int(4) UNSIGNED NULL DEFAULT 0 COMMENT '节点类别',
+  `source` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '添加源',
+  `remark` varchar(60) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '备注',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_tender_id`(`bid`) USING BTREE,
+  INDEX `idx_template_pid`(`tree_pid`) USING BTREE,
+  INDEX `idx_level`(`level`) USING BTREE,
+  INDEX `idx_full_path`(`bid`, `full_path`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '控制目标数据' ROW_FORMAT = Dynamic;
+
+ALTER TABLE `zh_budget_final`
+ADD COLUMN `ctrl_dgn_qty1` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '控制目标-设计数量1' AFTER `zb_tp`,
+ADD COLUMN `ctrl_dgn_qty2` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '控制目标-设计数量1' AFTER `ctrl_dgn_qty1`,
+ADD COLUMN `ctrl_dgn_qty` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '控制目标-设计数量1/设计数量2' AFTER `ctrl_dgn_qty2`,
+ADD COLUMN `ctrl_dgn_price` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '控制目标-经济指标' AFTER `ctrl_dgn_qty`,
+ADD COLUMN `ctrl_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '控制目标-金额' AFTER `ctrl_dgn_price`;
+
+ALTER TABLE `zh_budget_std`
+ADD COLUMN `ctrl_chapter_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '控制目标-项目节-id列表(\',\'分隔)' AFTER `zb_bills_id`,
+ADD COLUMN `ctrl_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '控制目标-新建模板-id列表(\',\'分隔)' AFTER `ctrl_chapter_id`,
+ADD COLUMN `ctrl_bills_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '控制目标-工程量清单-id列表(\',\'分隔)' AFTER `ctrl_template_id`;
+
+ALTER TABLE `zh_sub_project` ADD `shenpi` VARCHAR(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '审批流程设置' AFTER `payment_setting`;
+
+ALTER TABLE `zh_shenpi_group`
+ADD COLUMN `spid` varchar(255) NULL DEFAULT NULL COMMENT '项目id' AFTER `id`;
+
+ALTER TABLE `zh_shenpi_audit`
+ADD COLUMN `spid` varchar(255) NULL DEFAULT NULL COMMENT '项目id' AFTER `id`,
+MODIFY COLUMN `tid` int(11) NULL DEFAULT NULL COMMENT '标段id' AFTER `spid`;
+
+CREATE TABLE `zh_contract_sp_audit` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) NULL DEFAULT NULL COMMENT '项目id',
+  `tid` int NULL DEFAULT NULL COMMENT '标段id',
+  `cid` varchar(100) NULL DEFAULT NULL COMMENT '合同详情id',
+  `cpid` int NULL DEFAULT NULL COMMENT '合同支付id',
+  `aid` int NOT NULL COMMENT '审批人id',
+  `order` int NOT NULL COMMENT '审批顺序',
+  `times` int NOT NULL COMMENT '审批次数',
+  `status` tinyint(1) NOT NULL COMMENT '审批状态',
+  `begin_time` datetime NULL DEFAULT NULL COMMENT '开始审批时间',
+  `end_time` datetime NULL DEFAULT NULL COMMENT '结束审批时间',
+  `opinion` varchar(1000) NULL DEFAULT NULL COMMENT '审批意见',
+  `audit_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '审批类型(1个人,2会签,3或签)',
+  `audit_order` tinyint(4) NOT NULL DEFAULT 0 COMMENT '审批顺序',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT = '合同详情和合同支付,回款审批人表';
+
+ALTER TABLE `zh_ancillary_gcl`
+ADD COLUMN `pid` varchar(36) NOT NULL DEFAULT '' COMMENT '计量单元id' AFTER `lid`,
+ADD COLUMN `calc_template` varchar(36) NOT NULL DEFAULT '' COMMENT '明细计算模板' AFTER `update_time`;
+
+CREATE TABLE `zh_ancillary_gcl_detail`  (
+  `id` varchar(36) NOT NULL COMMENT 'uuid',
+  `tid` int(11) NOT NULL COMMENT '标段id(zh_tender.id)',
+  `lid` varchar(36) NOT NULL COMMENT '台账id(zh_ledger.id)',
+  `pid` varchar(36) NOT NULL COMMENT '计量单元id(zh_pos.id)',
+  `ag_id` varchar(36) NOT NULL COMMENT '附属工程量id(zh_ancillary_gcl.id)',
+  `agd_order` int(11) NOT NULL COMMENT '排序',
+  `create_user_id` int(11) NOT NULL COMMENT '新增用户id(zh_project_account.id)',
+  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '新增时间',
+  `update_user_id` int(11) NOT NULL COMMENT '最后修改用户id(zh_project_account.id)',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
+  `qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数量',
+  `expr` varchar(255) NOT NULL DEFAULT '' COMMENT '数量计算式',
+  `str1` varchar(255) NOT NULL DEFAULT '' COMMENT '文本1',
+  `str2` varchar(255) NOT NULL DEFAULT '' COMMENT '文本2',
+  `str3` varchar(255) NOT NULL DEFAULT '' COMMENT '文本3',
+  `str4` varchar(255) NOT NULL DEFAULT '' COMMENT '文本4',
+  `spec` varchar(50) NOT NULL DEFAULT '' COMMENT '规格',
+  `num_a` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值a',
+  `num_b` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值b',
+  `num_c` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值c',
+  `num_d` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值d',
+  `num_e` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值e',
+  `num_f` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值f',
+  `num_g` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值g',
+  `num_h` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值h',
+  `num_i` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值i',
+  `num_j` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值j',
+  `num_k` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值k',
+  `num_l` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值l',
+  `num_m` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值m',
+  `num_n` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值n',
+  `num_o` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值o',
+  `num_p` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值p',
+  `num_q` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值q',
+  `num_r` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值r',
+  `num_s` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值s',
+  `num_t` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值t',
+  `num_u` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值u',
+  PRIMARY KEY (`id`)
+);
+
+ALTER TABLE `zh_tender_cache`
+MODIFY COLUMN `stage_flow_cur_info` varchar(5000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期-当前流程信息(json)' AFTER `stage_flow_cur_uid`,
+MODIFY COLUMN `stage_flow_pre_info` varchar(5000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期-上一流程信息(json)' AFTER `stage_flow_pre_uid`;
+
+CREATE TABLE `zh_contract_supplement` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `tid` int(11) DEFAULT NULL COMMENT '标段id',
+  `contract_type` tinyint(1) NOT NULL COMMENT '合同类型(1是支出,2是收入)',
+  `cid` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '合同详情id',
+  `uid` int(11) DEFAULT NULL COMMENT '创建人id',
+  `need_shenpi` tinyint(1) DEFAULT NULL COMMENT '是否需要审批',
+  `status` tinyint(2) DEFAULT '1' COMMENT '审批状态',
+  `times` tinyint(3) DEFAULT '1' COMMENT '审批次数',
+  `sp_group` int(11) DEFAULT '0' COMMENT '固定审批组id',
+  `final_auditor_str` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '终审人相关(cache)',
+  `code` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '编号',
+  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '名称',
+  `price` decimal(30,8) DEFAULT '0.00000000' COMMENT '金额',
+  `party_a` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '甲方',
+  `party_b` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '乙方',
+  `sign_time` datetime DEFAULT NULL COMMENT '签订时间',
+  `address` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '签订地点',
+  `content` varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '补充原因及内容',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='合同补充表';
+
+CREATE TABLE `zh_contract_supplement_attachment` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `tid` int(11) DEFAULT NULL COMMENT '标段id',
+  `contract_type` tinyint(1) NOT NULL COMMENT '合同类型(1是支出,2是收入)',
+  `cid` varchar(100) COLLATE utf8_unicode_ci NOT NULL COMMENT '合同详情id',
+  `csid` int(11) NOT NULL 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_cid` (`csid`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='合同补充附件表';
+
+ALTER TABLE `zh_sub_project`
+MODIFY COLUMN `fun_rela` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '功能设置(json.stringify)' AFTER `page_show`;
+
+ALTER TABLE `zh_calc_tmpl`
+ADD COLUMN `sub_spread_cache` json NULL COMMENT '子数据spreadjs表头设置缓存' AFTER `spread_cache`;
+------------------------------------
+-- 表数据
+------------------------------------
+
+ALTER TABLE `zh_cost_stage`
+	DEFAULT CHARACTER SET=utf8,
+	COLLATE=utf8_unicode_ci
+;