Explorar o código

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

Tony Kang hai 2 semanas
pai
achega
ace6f3075c
Modificáronse 51 ficheiros con 3050 adicións e 161 borrados
  1. 1 0
      app/const/audit.js
  2. 9 0
      app/const/project_setting.js
  3. 2 2
      app/const/shenpi.js
  4. 1 1
      app/const/tender_info.js
  5. 107 7
      app/controller/cost_controller.js
  6. 12 0
      app/controller/dashboard_controller.js
  7. 118 67
      app/controller/datacollect_controller.js
  8. 11 1
      app/controller/sub_proj_setting_controller.js
  9. 2 1
      app/controller/template_controller.js
  10. 1 0
      app/controller/tender_controller.js
  11. 5 2
      app/lib/budget_final.js
  12. 3 2
      app/lib/ledger.js
  13. 257 0
      app/lib/rptCustomData.js
  14. BIN=BIN
      app/public/images/juecedaping08.png
  15. 17 8
      app/public/js/budget_compare.js
  16. 2 2
      app/public/js/cost_gather.js
  17. 2 1
      app/public/js/cost_stage_book.js
  18. 550 0
      app/public/js/cost_stage_duty.js
  19. 1 1
      app/public/js/cost_stage_ledger.js
  20. 1 1
      app/router.js
  21. 1 1
      app/service/budget.js
  22. 1 0
      app/service/budget_final_list.js
  23. 1 1
      app/service/contract.js
  24. 21 7
      app/service/cost_stage.js
  25. 4 4
      app/service/cost_stage_book.js
  26. 403 0
      app/service/cost_stage_duty.js
  27. 11 0
      app/service/report.js
  28. 1 0
      app/service/tender_permission.js
  29. 2 0
      app/view/cost/analysis_menu_list.ejs
  30. 5 4
      app/view/cost/audit_btn.ejs
  31. 2 0
      app/view/cost/book_menu_list.ejs
  32. 82 0
      app/view/cost/duty.ejs
  33. 2 0
      app/view/cost/duty_menu_list.ejs
  34. 2 0
      app/view/cost/duty_modal.ejs
  35. 2 0
      app/view/cost/ledger_menu_list.ejs
  36. 12 0
      app/view/cost/list_menu_list.ejs
  37. 2 1
      app/view/cost/stage_memu.ejs
  38. 1 0
      app/view/cost/stage_mini_menu.ejs
  39. 23 1
      app/view/dashboard/index.ejs
  40. 18 0
      app/view/dashboard/workspace.ejs
  41. 2 4
      app/view/datacollect/index.ejs
  42. 1 5
      app/view/datacollect/index4GY18Y.ejs
  43. 2 6
      app/view/datacollect/index_cost.ejs
  44. 1017 0
      app/view/datacollect/index_tz.ejs
  45. 0 7
      app/view/layout/menu.ejs
  46. 1 1
      app/view/safe/inspection_information.ejs
  47. 62 18
      app/view/sp_setting/datacollect.ejs
  48. 204 3
      app/view/sp_setting/datacollect_modal.ejs
  49. 2 2
      app/view/template/pos_calc.ejs
  50. 24 0
      config/web.js
  51. 37 0
      sql/update.sql

+ 1 - 0
app/const/audit.js

@@ -1656,6 +1656,7 @@ const pushType = {
     costStageLedger: 16,
     costStageBook: 17,
     costStageAnalysis: 18,
+    costStageDuty: 19,
 };
 
 module.exports = {

+ 9 - 0
app/const/project_setting.js

@@ -106,10 +106,19 @@ const daPing06Set = {
     cb_value: null,
     cb_show: [],
 };
+const daPing08Set = {
+    tenders: [
+        { id: 1, a: 0, b: 0 },
+        { id: 2, a: 0, b: 0 },
+        { id: 3, a: 0, b: 0 },
+        { id: 4, a: 0, b: 0 },
+    ],
+};
 module.exports = {
     noticeSetting: notice_setting,
     colSet,
     defaultProjectColSet,
     defaultColSet,
     daPing06Set,
+    daPing08Set,
 };

+ 2 - 2
app/const/shenpi.js

@@ -34,7 +34,7 @@ const cost_sp_type = {
     cost_stage_ledger: 13,
     cost_stage_book: 14,
     cost_stage_analysis: 15,
-    cost_duty: 17,
+    cost_stage_duty: 17,
 };
 // const sp_name = [];
 // sp_name[sp_type.advance] = '预付款审批';
@@ -60,10 +60,10 @@ const sp_lc = [
     { code: 'contract', type: sp_type.contract, name: '标段合同审批' },
 ];
 const cost_sp_lc = [
+    { code: 'cost_stage_duty', type: cost_sp_type.cost_stage_duty, name: '责任成本' },
     { code: 'cost_stage_ledger', type: cost_sp_type.cost_stage_ledger, name: '成本报审' },
     { code: 'cost_stage_book', type: cost_sp_type.cost_stage_book, name: '财务账面' },
     { code: 'cost_stage_analysis', type: cost_sp_type.cost_stage_analysis, name: '成本分析' },
-    { code: 'cost_duty', type: cost_sp_type.cost_duty, name: '责任成本' },
 ];
 
 const sp_status = {

+ 1 - 1
app/const/tender_info.js

@@ -200,7 +200,7 @@ const defaultInfo = {
         cost_stage_ledger: 1,
         cost_stage_book: 1,
         cost_stage_analysis: 1,
-        cost_duty: 1,
+        cost_stage_duty: 1,
         contract: 1,
     },
     ledger_check: {

+ 107 - 7
app/controller/cost_controller.js

@@ -87,6 +87,10 @@ module.exports = app => {
             }
         }
 
+        async checkDutyRela(ctx) {
+            ctx.tender.costDuty = await ctx.service.costStage.getStageByOrder(ctx.tender.id, 'duty', 1);
+        }
+
         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) {
@@ -107,6 +111,7 @@ module.exports = app => {
         }
         async ledger(ctx) {
             try {
+                await this.checkDutyRela(ctx);
                 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);
@@ -126,6 +131,7 @@ module.exports = app => {
         }
         async book(ctx) {
             try {
+                await this.checkDutyRela(ctx);
                 const stage_type = this.ctx.service.costStage.stageType.book.key;
                 const stages = await this.getTypeStages(ctx, stage_type);
                 for (const s of stages) {
@@ -152,6 +158,7 @@ module.exports = app => {
         }
         async analysis(ctx) {
             try {
+                await this.checkDutyRela(ctx);
                 const stage_type = this.ctx.service.costStage.stageType.analysis.key;
                 const stages = await this.getTypeStages(ctx, stage_type);
                 // todo 兼容以ledger做关联的情况,届时只需在标段或者项目上设置这个类型即可。
@@ -180,14 +187,17 @@ module.exports = app => {
 
         async addStage(ctx) {
             const stage_type = ctx.request.body.stage_type;
+            const hintType  = stage_type === 'duty' ? '数据' : '期'
             try {
-                if (!ctx.permission.cost[stage_type + '_add']) throw '您无权创建';
+                if (!ctx.permission.cost[stage_type + '_add']) throw '您无权创建' + hintType;
                 const stage_date = ctx.request.body.date;
                 if (stage_type === 'ledger' && !stage_date) throw '请选择年月';
 
-                const stages = await ctx.service.costStage.getAllStages(ctx.tender.id, stage_type, 'DESC');
-                const unCompleteCount = stages.filter(s => { return s.status !== audit.common.status.checked; }).length;
-                if (unCompleteCount.length > 0) throw '最新一期未审批通过,请审批通过后再新增';
+                if (stage_type !== 'duty') {
+                    const stages = await ctx.service.costStage.getAllStages(ctx.tender.id, stage_type, 'DESC');
+                    const unCompleteCount = stages.filter(s => { return s.status !== audit.common.status.checked; }).length;
+                    if (unCompleteCount.length > 0) throw '最新一期未审批通过,请审批通过后再新增';
+                }
 
                 let newStage;
                 if (stage_type === 'ledger') {
@@ -207,14 +217,22 @@ module.exports = app => {
                     if (!relaStage) throw '选择的关联期不存在';
                     newStage = await ctx.service.costStage.add(ctx.tender.id, stage_type, relaStage.stage_date, { sid: relaStage.id, sorder: relaStage.stage_order });
                     await this.ctx.service.costStageAnalysis.reCalcProfit(newStage.id);
+                } else if (stage_type === 'duty') {
+                    newStage = await ctx.service.costStage.getStageByOrder(ctx.tender.id, stage_type, 1);
+                    if (newStage) throw '已新增责任成本,请勿重复新增';
+                    newStage = await ctx.service.costStage.add(ctx.tender.id, stage_type, '');
                 }
 
-                if (!newStage) throw '新增期失败';
+                if (!newStage) throw `新增${hintType}失败`;
                 ctx.redirect(`/sp/${ctx.subProject.id}/cost/tender/${ctx.tender.id}/${stage_type}/${newStage.stage_order}/stage`);
             } catch (err) {
                 ctx.log(err);
-                ctx.postError(err, '新增期失败');
-                ctx.redirect(`/sp/${ctx.subProject.id}/cost/tender/${ctx.tender.id}/${stage_type}`);
+                ctx.postError(err, `新增${hintType}失败`);
+                if (stage_type === 'duty') {
+                    ctx.redirect(`/sp/${ctx.subProject.id}/cost/tender/${ctx.tender.id}/ledger`)
+                } else {
+                    ctx.redirect(`/sp/${ctx.subProject.id}/cost/tender/${ctx.tender.id}/${stage_type}`);
+                }
             }
         }
         async delStage(ctx) {
@@ -252,6 +270,7 @@ module.exports = app => {
         async stage(ctx) {
             const stageTypeInfo = ctx.service.costStage.stageType[ctx.costStage.stage_type];
             try {
+                await this.checkDutyRela(ctx);
                 await this._getStageAuditViewData(ctx);
                 // 流程审批人相关数据
                 const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
@@ -483,6 +502,33 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
+        async _dutyLoad(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const filter = data.filter.split(';');
+                const responseData = { err: 0, msg: '', data: {}, hpack: [] };
+                for (const f of filter) {
+                    switch (f) {
+                        case 'bills':
+                            responseData.data.bills = ctx.costStage.readOnly && !ctx.costStage.canCheck
+                                ? await ctx.service.costStageDuty.getReadData(ctx.costStage)
+                                : await ctx.service.costStageDuty.getEditData(ctx.costStage);
+                            break;
+                        case 'extra':
+                            responseData.data.extra = ctx.costStage.extra_info;
+                            break;
+                        default:
+                            responseData.data[f] = [];
+                            break;
+                    }
+                }
+
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
         async stageLoad(ctx) {
             const updateFun = `_${ctx.costStage.stage_type}Load`;
             if (this[updateFun]) {
@@ -492,6 +538,58 @@ module.exports = app => {
             }
         }
 
+        async _dutyBillsBase(stage, type, data) {
+            if (isNaN(data.id) || data.id <= 0) throw '数据错误';
+            if (type !== 'add') {
+                if (isNaN(data.count) || data.count <= 0) data.count = 1;
+            }
+            switch (type) {
+                case 'add':
+                    return await this.ctx.service.costStageDuty.addDutyNode(stage, data.id, data.count);
+                case 'delete':
+                    return await this.ctx.service.costStageDuty.delete(stage.id, data.id, data.count);
+                case 'up-move':
+                    return await this.ctx.service.costStageDuty.upMoveNode(stage.id, data.id, data.count);
+                case 'down-move':
+                    return await this.ctx.service.costStageDuty.downMoveNode(stage.id, data.id, data.count);
+                case 'up-level':
+                    return await this.ctx.service.costStageDuty.upLevelNode(stage.id, data.id, data.count);
+                case 'down-level':
+                    return await this.ctx.service.costStageDuty.downLevelNode(stage.id, data.id, data.count);
+            }
+        }
+        async _dutyUpdate(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.target) throw '数据错误';
+                const responseData = { err: 0, msg: '', data: {} };
+
+                if (data.target === 'ledger') {
+                    if (!data.postType || !data.postData) throw '数据错误';
+                    switch (data.postType) {
+                        case 'add':
+                        case 'delete':
+                        case 'up-move':
+                        case 'down-move':
+                        case 'up-level':
+                        case 'down-level':
+                            responseData.data = await this._dutyBillsBase(ctx.costStage, data.postType, data.postData);
+                            break;
+                        case 'update':
+                            responseData.data = await this.ctx.service.costStageDuty.updateCalc(ctx.costStage, data.postData);
+                            break;
+                        default:
+                            throw '未知操作';
+                    }
+                } else if (data.target === 'extra') {
+                    responseData.data = await this.ctx.service.costStage.saveExtraInfo(ctx.costStage, data.data);
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '数据错误');
+            }
+        }
         async _billsBase(stage, type, data) {
             if (isNaN(data.id) || data.id <= 0) throw '数据错误';
             if (type !== 'add') {
@@ -893,6 +991,7 @@ module.exports = app => {
         async compare(ctx) {
             const stageTypeInfo = ctx.service.costStage.stageType[ctx.costStage.stage_type];
             try {
+                await this.checkDutyRela(ctx);
                 await this._getStageAuditViewData(ctx);
                 // 流程审批人相关数据
                 const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
@@ -926,6 +1025,7 @@ module.exports = app => {
 
         async gather(ctx) {
             try {
+                await this.checkDutyRela(ctx);
                 const ledgerStages = await this.getTypeStages(ctx, 'ledger', 'ASC');
                 const renderData = {
                     ledgerStages,

+ 12 - 0
app/controller/dashboard_controller.js

@@ -47,6 +47,7 @@ module.exports = app => {
             const allAuditCostStageLedger = await ctx.service.costStageAudit.getAuditStage('ledger', ctx.session.sessionUser.accountId);
             const allAuditCostStageBook = await ctx.service.costStageAudit.getAuditStage('book', ctx.session.sessionUser.accountId);
             const allAuditCostStageAnalysis = await ctx.service.costStageAudit.getAuditStage('analysis', ctx.session.sessionUser.accountId);
+            const allAuditCostStageDuty = await ctx.service.costStageAudit.getAuditStage('duty', ctx.session.sessionUser.accountId);
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             const auditShenpiTenders = await ctx.service.ledgerAudit.getDonesByAudit(ctx.session.sessionUser.accountId);
             const auditShenpiStages = await ctx.service.stageAudit.getDonesByAudit(ctx.session.sessionUser.accountId);
@@ -66,6 +67,7 @@ module.exports = app => {
             const auditShenpiCostStageLedger = await ctx.service.costStageAudit.getDonesByAudit('ledger', ctx.session.sessionUser.accountId);
             const auditShenpiCostStageBook = await ctx.service.costStageAudit.getDonesByAudit('book', ctx.session.sessionUser.accountId);
             const auditShenpiCostStageAnalysis = await ctx.service.costStageAudit.getDonesByAudit('analysis', ctx.session.sessionUser.accountId);
+            const auditShenpiCostStageDuty = await ctx.service.costStageAudit.getDonesByAudit('duty', ctx.session.sessionUser.accountId);
             const dashboardStatus = {
                 all: 0,
                 dashboard: 0,
@@ -90,6 +92,7 @@ module.exports = app => {
                     costStageLedger: 0,
                     costStageBook: 0,
                     costStageAnalysis: 0,
+                    costStageDuty: 0,
                 },
             };
             let noticeList = [];
@@ -133,6 +136,8 @@ module.exports = app => {
                 noticeList.push(...ctx.helper.addKeyValue4ObjArray(spCostStageBook, 'shenpi_type', 'costStageBook'));
                 const spCostStageAnalysis = subProject.page_show.cost ? ctx.helper._.filter(auditShenpiCostStageAnalysis, { spid: subProject.id }) : [];
                 noticeList.push(...ctx.helper.addKeyValue4ObjArray(spCostStageAnalysis, 'shenpi_type', 'costStageAnalysis'));
+                const spCostStageDuty = subProject.page_show.cost ? ctx.helper._.filter(auditShenpiCostStageDuty, { spid: subProject.id }) : [];
+                noticeList.push(...ctx.helper.addKeyValue4ObjArray(spCostStageDuty, 'shenpi_type', 'costStageDuty'));
             }
             const noticeDayList = [];
             noticeList = ctx.helper._.orderBy(noticeList, ['shenpi_time'], ['desc']);
@@ -180,6 +185,8 @@ module.exports = app => {
             dashboardShenpis.push(...auditCostStageBook);
             const auditCostStageAnalysis = await this.auditSet(ctx, allAuditCostStageAnalysis, subProjects, dashboardStatus, 'costStageAnalysis');
             dashboardShenpis.push(...auditCostStageAnalysis);
+            const auditCostStageDuty = await this.auditSet(ctx, allAuditCostStageDuty, subProjects, dashboardStatus, 'costStageDuty');
+            dashboardShenpis.push(...auditCostStageDuty);
             // console.log(ctx.helper._.orderBy(dashboardShenpis, ['start_audit', 'shenpi_time'], ['desc', 'desc']));
             const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
             // 获取销售人员数据
@@ -335,6 +342,7 @@ module.exports = app => {
                         case 'costStageLedger':
                         case 'costStageBook':
                         case 'costStageAnalysis':
+                        case 'costStageDuty':
                             if (!sp.page_show.cost) closeType = true;
                             calcTime = t.create_time;
                             break;
@@ -390,6 +398,7 @@ module.exports = app => {
             const auditCostStageLedger = ctx.subProject.page_show.cost ? await ctx.service.costStageAudit.getAuditStage('ledger', ctx.session.sessionUser.accountId, ctx.subProject.id) : [];
             const auditCostStageBook = ctx.subProject.page_show.cost ? await ctx.service.costStageAudit.getAuditStage('book', ctx.session.sessionUser.accountId, ctx.subProject.id) : [];
             const auditCostStageAnalysis = ctx.subProject.page_show.cost ? await ctx.service.costStageAudit.getAuditStage('analysis', ctx.session.sessionUser.accountId, ctx.subProject.id) : [];
+            const auditCostStageDuty = ctx.subProject.page_show.cost ? await ctx.service.costStageAudit.getAuditStage('duty', ctx.session.sessionUser.accountId, ctx.subProject.id) : [];
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             const noticeList = await ctx.service.noticePush.getNotice(ctx.session.sessionProject.id, pa.id, ctx.subProject.id);
             const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
@@ -427,6 +436,7 @@ module.exports = app => {
                 shenpi_count.push({ count: await ctx.service.costStageAudit.getCountByChecked('ledger', ctx.session.sessionUser.accountId, ctx.subProject.id), name: '成本报审' });
                 shenpi_count.push({ count: await ctx.service.costStageAudit.getCountByChecked('book', ctx.session.sessionUser.accountId, ctx.subProject.id), name: '财务账面' });
                 shenpi_count.push({ count: await ctx.service.costStageAudit.getCountByChecked('analysis', ctx.session.sessionUser.accountId, ctx.subProject.id), name: '成本分析' });
+                shenpi_count.push({ count: await ctx.service.costStageAudit.getCountByChecked('duty', ctx.session.sessionUser.accountId, ctx.subProject.id), name: '责任成本' });
             }
             // shenpi_count.push({ count: await ctx.service.advanceAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '预付款' });
             const total_count = ctx.app._.sumBy(shenpi_count, 'count');
@@ -448,6 +458,7 @@ module.exports = app => {
                 ctx.subProject.page_show.cost ? await ctx.service.costStageAudit.getLastEndTimeByChecked('ledger', ctx.session.sessionUser.accountId, ctx.subProject.id) : null,
                 ctx.subProject.page_show.cost ? await ctx.service.costStageAudit.getLastEndTimeByChecked('book', ctx.session.sessionUser.accountId, ctx.subProject.id) : null,
                 ctx.subProject.page_show.cost ? await ctx.service.costStageAudit.getLastEndTimeByChecked('analysis', ctx.session.sessionUser.accountId, ctx.subProject.id) : null,
+                ctx.subProject.page_show.cost ? await ctx.service.costStageAudit.getLastEndTimeByChecked('duty', ctx.session.sessionUser.accountId, ctx.subProject.id) : null,
             ];
             const last_time = ctx.app._.max(shenpi_lastime);
             // console.log(ctx.app._.max(shenpi_lastime), ctx.helper.calcDayNum(last_time));
@@ -471,6 +482,7 @@ module.exports = app => {
                 auditCostStageLedger,
                 auditCostStageBook,
                 auditCostStageAnalysis,
+                auditCostStageDuty,
                 shenpi_count,
                 total_count,
                 last_day: ctx.helper.calcDayNum(last_time),

+ 118 - 67
app/controller/datacollect_controller.js

@@ -63,23 +63,33 @@ module.exports = app => {
                 // 默认坐标,否则则取办事处坐标
                 const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
                 ctx.subProject.data_collect_pages = ctx.subProject.data_collect_pages ? ctx.subProject.data_collect_pages.split(',') : [];
-                // 判断是否只有'7',如果只有7,则不用获取其它数据
-                let only_cost = false;
-                if (ctx.subProject.data_collect_pages.length === 1 && ctx.subProject.data_collect_pages[0] === '7') {
-                    only_cost = true;
-                }
+                // 判断ctx.subProject.data_collect_pages如果只有7或者只有8或者只有7,8,则不用获取其它数据
+                const pages = ctx.subProject.data_collect_pages;
+                const only_cost =
+                    (pages.length === 1 && (pages[0] === '7' || pages[0] === '8')) ||
+                    (pages.length === 2 &&
+                        pages.includes('7') &&
+                        pages.includes('8'));
                 let noticeList = [];
                 if (!only_cost) {
                     noticeList = await ctx.service.noticePush.getNoticeByDataCollect(ctx.session.sessionProject.id, tenderidList);
                 }
                 if (ctx.params.index) {
                     ctx.subProject.data_collect = parseInt(ctx.params.index);
+                    ctx.session.sessionSubProj = {
+                        data_collect: ctx.subProject.data_collect,
+                    };
+                } else {
+                    ctx.session.sessionSubProj = {
+                        data_collect: ctx.subProject.data_collect,
+                    };
                 }
-                const is_cost = ctx.subProject.data_collect_pages.includes('7') && ctx.subProject.data_collect === 7;
+                // const is_cost = ctx.subProject.data_collect_pages.includes('7') && ctx.subProject.data_collect === 7;
                 const is_dz2 = ['P0505', 'P0506', 'P1201', 'P1202', 'GY18Y', 'GYJJ1', 'P1103', 'KLG25', 'I2U5B', 'SEN48'].indexOf(ctx.session.sessionProject.code) !== -1
                     && ctx.subProject.data_collect_pages.includes('6') && ctx.subProject.data_collect === 6;
-                const is_dz3 = ctx.subProject.data_collect_pages.includes('8') && ctx.subProject.data_collect === 8;
-                const shj_url = 'http://112.74.42.187:3300/#/project/viewer?projectId=b03i54j0j8ai';
+                const is_dzcb = ['P0505', 'P0506', 'P1201', 'P1202', 'P1002', 'GY18Y', 'Y3T2Q'].indexOf(ctx.session.sessionProject.code) !== -1 && ctx.subProject.data_collect_pages.includes('7') && ctx.subProject.data_collect === 7;// 成本管理定制大屏
+                const is_dztz = ['P0505', 'P0506', 'P1201', 'P1202', 'P1002', 'Y3T2Q'].indexOf(ctx.session.sessionProject.code) !== -1 && ctx.subProject.data_collect_pages.includes('8') && ctx.subProject.data_collect === 8;// 投资总控定制大屏
+                // const shj_url = 'http://112.74.42.187:3300/#/project/viewer?projectId=b03i54j0j8ai';
                 const renderData = {
                     projectData,
                     noticeList,
@@ -93,10 +103,12 @@ module.exports = app => {
                     acAdvance: auditConst.advance,
                     pushType: auditConst.pushType,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.datacollect.index),
-                    shj_url,
+                    // shj_url,
                 };
-                if (is_cost) {
+                if (is_dzcb) {
                     await this.layout('datacollect/index_cost.ejs', renderData);
+                } else if (is_dztz) {
+                    await this.layout('datacollect/index_tz.ejs', renderData);
                 } else if (is_dz2) {
                     // 判断并更新common_json
                     if (!ctx.subProject.common_json) {
@@ -111,8 +123,8 @@ module.exports = app => {
                     }
                     renderData.dpCategory = dpCategory;
                     await this.layout('datacollect/index4GY18Y.ejs', renderData);
-                } else if (is_dz3) {
-                    ctx.redirect(shj_url);
+                // } else if (is_dz3) {
+                //     ctx.redirect(shj_url);
                 } else {
                     await this.layout('datacollect/index.ejs', renderData);
                 }
@@ -128,67 +140,66 @@ module.exports = app => {
                 const responseData = { err: 0, msg: '', data: {} };
                 // const tenderidList = [];
                 // const noticeList = await ctx.service.noticePush.getNoticeByDataCollect(ctx.session.sessionProject.id, tenderidList);
-                const dcTenders = await ctx.service.datacollectTender.getList(ctx.session.sessionProject.id, ctx.subProject.id);
-                const noTids = ctx.app._.map(dcTenders, 'tid');
-                let tenderList = await ctx.service.tender.getList('', null, 1);
-                // let tenderList = await this.ctx.service.tender.getBuildList('', null, 1);
-                tenderList = ctx.app._.filter(tenderList, function(item) {
-                    return ctx.app._.indexOf(noTids, item.id) === -1;
-                });
-                // 判断是否只有'7',如果只有7,则不用获取其它数据
-                let only_cost = false;
-                if (ctx.subProject.data_collect_pages.length === 1 && ctx.subProject.data_collect_pages[0] === '7') {
-                    only_cost = true;
-                }
-                const have_cost = ctx.subProject.data_collect_pages.includes('7');
+                const have_cost = ctx.session.sessionSubProj.data_collect === 7 || ctx.session.sessionSubProj.data_collect === 8;
                 const thisMonth = new Date();
                 const [startMonth, endMonth] = ctx.helper.getStartEndMonth(thisMonth);
                 // 统计方法总时长
                 // let sumStageByDataCollectTime = 0;
-                for (const t of tenderList) {
-                    // 用标段管理的方法获取t数据
-                    if (!only_cost) {
-                        await this.ctx.service.tenderCache.loadTenderCache(t, '');
-                        t.total_price = t.ledger_tp && t.ledger_tp.total_price ? t.ledger_tp.total_price : 0;
-                        [t.change_tp, t.change_p_tp, t.change_n_tp, t.change_valuation_tp, t.change_unvaluation_tp] = await ctx.service.change.getChangeTp(t.id);
-                        // t.material_tp = await ctx.service.material.getSumMaterial(t.id);
-                        // 获取本标段 本月计量期审批通过数目,变更令审批通过数目,台账修订通过数目,材料调差通过数目
-                        t.month_stage_num = await ctx.service.stageAudit.getNumByMonth(t.id, startMonth, endMonth);
-                        t.month_change_num = await ctx.service.changeAudit.getNumByMonth(t.id, startMonth, endMonth);
-                        t.month_revise_num = await ctx.service.reviseAudit.getNumByMonth(t.id, startMonth, endMonth);
-                        t.month_material_num = await ctx.service.materialAudit.getNumByMonth(t.id, startMonth, endMonth);
-                        // 获取标段计量月统计及截止月累计计量
-                        // 这个慢,统计下面这个方法时长
-                        const stageList = await ctx.service.stage.getStageByDataCollect(t.id, t.stage_tp);
-                        // const stageList = [];
-                        const month_stage = [];
-                        for (const s of stageList) {
-                            const monthOneStage = ctx.app._.find(month_stage, { yearmonth: s.s_time });
-                            if (monthOneStage) {
-                                monthOneStage.tp = ctx.helper.add(monthOneStage.tp, s.tp);
-                                monthOneStage.end_yf_tp = ctx.helper.add(monthOneStage.end_yf_tp, s.yf_tp);
-                                monthOneStage.end_sf_tp = ctx.helper.add(monthOneStage.end_sf_tp, s.sf_tp);
-                            } else {
-                                const data = {
-                                    yearmonth: s.s_time,
-                                    tp: s.tp,
-                                    end_tp: s.end_tp,
-                                    end_yf_tp: s.yf_tp,
-                                    end_sf_tp: s.sf_tp,
-                                };
-                                month_stage.push(data);
+                const only_tz = ctx.session.sessionSubProj.data_collect === 8;
+                if (!only_tz) {
+                    const dcTenders = await ctx.service.datacollectTender.getList(ctx.session.sessionProject.id, ctx.subProject.id);
+                    const noTids = ctx.app._.map(dcTenders, 'tid');
+                    let tenderList = await ctx.service.tender.getList('', null, 1);
+                    // let tenderList = await this.ctx.service.tender.getBuildList('', null, 1);
+                    tenderList = ctx.app._.filter(tenderList, function(item) {
+                        return ctx.app._.indexOf(noTids, item.id) === -1;
+                    });
+                    for (const t of tenderList) {
+                        // 用标段管理的方法获取t数据
+                        if (!have_cost) {
+                            await this.ctx.service.tenderCache.loadTenderCache(t, '');
+                            t.total_price = t.ledger_tp && t.ledger_tp.total_price ? t.ledger_tp.total_price : 0;
+                            [t.change_tp, t.change_p_tp, t.change_n_tp, t.change_valuation_tp, t.change_unvaluation_tp] = await ctx.service.change.getChangeTp(t.id);
+                            // t.material_tp = await ctx.service.material.getSumMaterial(t.id);
+                            // 获取本标段 本月计量期审批通过数目,变更令审批通过数目,台账修订通过数目,材料调差通过数目
+                            t.month_stage_num = await ctx.service.stageAudit.getNumByMonth(t.id, startMonth, endMonth);
+                            t.month_change_num = await ctx.service.changeAudit.getNumByMonth(t.id, startMonth, endMonth);
+                            t.month_revise_num = await ctx.service.reviseAudit.getNumByMonth(t.id, startMonth, endMonth);
+                            t.month_material_num = await ctx.service.materialAudit.getNumByMonth(t.id, startMonth, endMonth);
+                            // 获取标段计量月统计及截止月累计计量
+                            // 这个慢,统计下面这个方法时长
+                            const stageList = await ctx.service.stage.getStageByDataCollect(t.id, t.stage_tp);
+                            // const stageList = [];
+                            const month_stage = [];
+                            for (const s of stageList) {
+                                const monthOneStage = ctx.app._.find(month_stage, { yearmonth: s.s_time });
+                                if (monthOneStage) {
+                                    monthOneStage.tp = ctx.helper.add(monthOneStage.tp, s.tp);
+                                    monthOneStage.end_yf_tp = ctx.helper.add(monthOneStage.end_yf_tp, s.yf_tp);
+                                    monthOneStage.end_sf_tp = ctx.helper.add(monthOneStage.end_sf_tp, s.sf_tp);
+                                } else {
+                                    const data = {
+                                        yearmonth: s.s_time,
+                                        tp: s.tp,
+                                        end_tp: s.end_tp,
+                                        end_yf_tp: s.yf_tp,
+                                        end_sf_tp: s.sf_tp,
+                                    };
+                                    month_stage.push(data);
+                                }
                             }
+                            t.month_stage = month_stage;
+                        }
+                        if (have_cost) {
+                            // 取最新已审批完成的成本管理-成本分析期数据
+                            t.cost_analysis = await ctx.service.costStage.getLastCheckedStage(t.id, 'analysis');
+                            t.stage_num = await ctx.service.stage.getNumByChecked(t.id);
+                            t.cost_ledger_num = await ctx.service.costStage.getNumByChecked(t.id, 'ledger');
+                            t.cost_analysis_num = await ctx.service.costStage.getNumByChecked(t.id, 'analysis');
+                            t.cost_book_num = await ctx.service.costStage.getNumByChecked(t.id, 'book');
                         }
-                        t.month_stage = month_stage;
-                    }
-                    if (have_cost) {
-                        // 取最新已审批完成的成本管理-成本分析期数据
-                        t.cost_analysis = await ctx.service.costStage.getLastCheckedStage(t.id, 'analysis');
-                        t.stage_num = await ctx.service.stage.getNumByChecked(t.id);
-                        t.cost_ledger_num = await ctx.service.costStage.getNumByChecked(t.id, 'ledger');
-                        t.cost_analysis_num = await ctx.service.costStage.getNumByChecked(t.id, 'analysis');
-                        t.cost_book_num = await ctx.service.costStage.getNumByChecked(t.id, 'book');
                     }
+                    responseData.data.tenderList = tenderList;
                 }
                 if (have_cost) {
                     const data = {};
@@ -206,10 +217,50 @@ module.exports = app => {
                         data.ctrl = await ctx.service.budgetCtrl.getData(budget.id);
                         data.real = await ctx.service.budgetReal.getData(budget.id);
                     }
+                    data.info = budget;
                     responseData.data.budget = data;
                 }
+                if (only_tz) {
+                    const commonJson = ctx.subProject.common_json ? JSON.parse(ctx.subProject.common_json) : null;
+                    const daping08Set = commonJson && commonJson.daPing08_set ? commonJson.daPing08_set : ctx.helper._.cloneDeep(projectSettingConst.daPing08Set);
+                    const tenderList = {
+                        1: {
+                            a: null,
+                            b: null,
+                            // name: '',
+                            // contract_price: 0,
+                            // end_gather_tp: 0,
+                            // end_profit: 0,
+                        },
+                        2: {
+                            a: null,
+                            b: null,
+                        },
+                        3: {
+                            a: null,
+                            b: null,
+                        },
+                        4: {
+                            a: null,
+                            b: null,
+                        },
+                    };
+                    for (const t of daping08Set.tenders) {
+                        tenderList[t.id] = {};
+                        const tendera = t.a ? await ctx.service.tender.getDataById(t.a) : null;
+                        if (tendera) {
+                            await this.ctx.service.tenderCache.loadTenderCache(tendera, '');
+                            tenderList[t.id].a = tendera;
+                            const tenderb = t.b ? await ctx.service.tender.getDataById(t.b) : null;
+                            if (tenderb) {
+                                await this.ctx.service.tenderCache.loadTenderCache(tenderb, '');
+                                tenderList[t.id].b = tenderb;
+                            }
+                        }
+                    }
+                    responseData.data.tenderList = tenderList;
+                }
                 // const tenderMapList = await ctx.service.tenderMap.getAllDataByCondition({ where: { tid: tender.id } });
-                responseData.data.tenderList = tenderList;
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);

+ 11 - 1
app/controller/sub_proj_setting_controller.js

@@ -444,6 +444,8 @@ module.exports = app => {
                 const tenders = await ctx.service.tender.getList('', null, 1);
                 const dcTenders = await ctx.service.datacollectTender.getList(ctx.subProject.project_id, ctx.subProject.id);
                 const is_dz2 = ['P0505', 'P0506', 'P1201', 'P1202', 'GY18Y', 'GYJJ1', 'P1103', 'KLG25', 'I2U5B', 'SEN48'].indexOf(ctx.session.sessionProject.code) !== -1 ? 6 : false;
+                const is_dzcb = ['P0505', 'P0506', 'P1201', 'P1202', 'GY18Y', 'Y3T2Q'].indexOf(ctx.session.sessionProject.code) !== -1 ? 7 : false;// 成本管理定制大屏
+                const is_dztz = ['P0505', 'P0506', 'P1201', 'P1202', 'GY18Y', 'Y3T2Q'].indexOf(ctx.session.sessionProject.code) !== -1 ? 8 : false;// 投资总控定制大屏
                 const renderData = {
                     dataCollectAudits,
                     accountList,
@@ -453,7 +455,8 @@ module.exports = app => {
                     dcTenders,
                     is_dz1: ['P0505', 'MI22U'].indexOf(ctx.session.sessionProject.code) !== -1 ? 5 : false,
                     is_dz2,
-                    is_dz3: 8,
+                    is_dzcb,
+                    is_dztz,
                     selfCategoryLevel: await ctx.service.projectAccount.getSelfCategoryLevel(this.ctx.session.sessionUser.accountId),
                 };
                 if (is_dz2) {
@@ -465,6 +468,10 @@ module.exports = app => {
                     const commonJson = ctx.subProject.common_json ? JSON.parse(ctx.subProject.common_json) : null;
                     renderData.daPing06Set = commonJson && commonJson.daPing06_set ? commonJson.daPing06_set : ctx.helper._.cloneDeep(projectSettingConst.daPing06Set);
                 }
+                if (is_dztz) {
+                    const commonJson = ctx.subProject.common_json ? JSON.parse(ctx.subProject.common_json) : null;
+                    renderData.daPing08Set = commonJson && commonJson.daPing08_set ? commonJson.daPing08_set : ctx.helper._.cloneDeep(projectSettingConst.daPing08Set);
+                }
                 await this.layout('sp_setting/datacollect.ejs', renderData, 'sp_setting/datacollect_modal.ejs');
             } catch (error) {
                 ctx.log(error);
@@ -530,6 +537,9 @@ module.exports = app => {
                     case 'save-dp06':
                         responseData.data = await ctx.service.subProject.saveCommonJson(ctx.subProject.id, 'daPing06_set', data.daPing06Set);
                         break;
+                    case 'save-dp08':
+                        responseData.data = await ctx.service.subProject.saveCommonJson(ctx.subProject.id, 'daPing08_set', data.daPing08Set);
+                        break;
                     default:
                         throw '参数有误';
                 }

+ 2 - 1
app/controller/template_controller.js

@@ -243,7 +243,8 @@ module.exports = app => {
                 const Cpd = require('../lib/crypt').cpd;
                 const cpd = new Cpd();
                 const data = await cpd.decrypt(filepath);
-                if (!data.col_set || !data.multi_header) throw '模板文件格式错误';
+                // if (!data.col_set || !data.multi_header) throw '模板文件格式错误';
+                if (!data.col_set) throw '模板文件格式错误';
                 if (template.type !== data.type) throw '导入的模板文件与当前模板类型不一致,不可导入';
 
                 const result =await ctx.service.calcTmpl.saveTemplate({ update: { id: template.id, col_set: data.col_set, multi_header: data.multi_header } });

+ 1 - 0
app/controller/tender_controller.js

@@ -1003,6 +1003,7 @@ module.exports = app => {
         }
         async shenpiSet(ctx) {
             try {
+                ctx.tender.costDuty = await this.ctx.service.costStage.getStageByOrder(ctx.tender.id, 'duty', 1);
                 const specShenpiType = this._getSpecShenpiUrl(ctx);
                 // 获取所有项目参与者
                 const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);

+ 5 - 2
app/lib/budget_final.js

@@ -319,13 +319,14 @@ class BudgetFinal {
     async _loadDeal(deal, stage) {
         if (!deal || deal.length === 0) return;
 
+        this.final.contract = deal;
         const helper = this.ctx.helper;
         const treeData = await this.ctx.service.contractTree.getAllDataByCondition({ where: { id: deal, spid: this.ctx.subProject.id, contract_type: 1 } });
         const contractData = await this.ctx.service.contract.getAllDataByCondition({ where: { id: deal, spid: this.ctx.subProject.id, contract_type: 1 } });
 
         const paySqlParam = [];
-        const timeFilter = stage.type === 'month' ? `AND pay_time <= ? ` : '';
-        if (stage.type === 'month') {
+        const timeFilter = stage.type === 'under-month' ? `AND pay_time <= ? ` : '';
+        if (stage.type === 'under-month') {
             paySqlParam.push(this.ctx.moment(stage.month, 'YYYY-MM').endOf('month').format('YYYY-MM-DD HH:mm:ss'));
         }
         const checkedFilter = stage.checked && this.ctx.subProject.page_show.openContractPaySubProjectShenpi ? ' AND status = 3 ' : '';
@@ -348,7 +349,9 @@ class BudgetFinal {
             calc: function (node, helper, decimal) {},
         });
         tree.loadDatas([...treeData, ...contractData]);
+        tree.calculateAll();
         this.finalTree.loadTree(tree, function (cur, source) {
+            if (source.c_code) cur.code = source.c_code;
             cur.total_price = helper.add(cur.total_price, source.total_price);
             cur.final_contract_tp = helper.add(cur.final_contract_tp, source.yf_tp);
             cur.final_tp = helper.add(cur.final_tp, source.yf_tp);

+ 3 - 2
app/lib/ledger.js

@@ -362,7 +362,7 @@ class baseTree {
         if (fun) {
             fun(node);
         } else if (this.setting.calc) {
-            this.setting.calc(node, this.ctx.helper, this.ctx.tender.info.decimal);
+            this.setting.calc(node, this.ctx.helper, this.ctx.tender ? this.ctx.tender.info.decimal : {});
         }
     }
     calculateAll(fun) {
@@ -698,9 +698,10 @@ class pos {
     }
 
     resortLedgerPos(ledgerPos) {
+        const orderField = this.setting.orderField || 'porder';
         if (ledgerPos instanceof Array) {
             ledgerPos.sort(function (a, b) {
-                return a.porder - b.porder;
+                return a[orderField] - b[orderField];
             })
         }
     }

+ 257 - 0
app/lib/rptCustomData.js

@@ -9,6 +9,7 @@
  */
 const auditConst = require('../const/audit');
 const Ledger = require('./ledger');
+const mergeChar = ';';
 
 /**
  * 季华项目 定制报表
@@ -712,7 +713,263 @@ class fjHelper {
     }
 }
 
+/**
+ * 华晶项目 定制报表
+ * 台账复核表:根据【台账顺序】列出所有清单、计量单元及明细、附属工程量及明细
+ */
+class huajingHelper {
+    constructor(ctx) {
+        this.ctx = ctx;
+        this.dataType = { jl: '计量', fs: '附属' };
+    }
+    async loadLedgerData(tid) {
+        this.ledger = new Ledger.billsTree(this.ctx, {
+            id: 'ledger_id',
+            pid: 'ledger_pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            keys: ['id', 'tender_id', 'ledger_id'],
+            stageId: 'id',
+            calcFields: [],
+            calc: function (node, helper, decimal) {},
+        });
+        const ledgerData = await this.ctx.service.ledger.getData(tid);
+        const ledgerExtraData = await this.ctx.service.ledgerExtra.getAllDataByCondition({ where: { tid } });
+        this.ctx.helper.assignRelaData(ledgerData, [
+            { data: ledgerExtraData, fields: ['calc_template'], prefix: '', relaId: 'id' },
+        ]);
+        this.ledger.loadDatas(ledgerData);
+
+        this.pos = new Ledger.pos({ id: 'id', ledgerId: 'lid' });
+        const posData = await this.ctx.service.pos.getAllDataByCondition({ where: { tid }});
+        this.pos.loadDatas(posData);
+
+        this.posCalcDetail = new Ledger.pos({ id: 'id', ledgerId: 'pid', orderField: 'pcd_order' });
+        const posCalcDetailData = await this.ctx.service.posCalcDetail.getAllDataByCondition({ where: { tid } });
+        this.posCalcDetail.loadDatas(posCalcDetailData);
+
+        this.ancGcl = new Ledger.pos({ id: 'id', ledgerId: 'pid', orderField: 'g_order' });
+        const ancGclData = await this.ctx.service.ancillaryGcl.getAllDataByCondition({ where: { tid } });
+        this.ancGcl.loadDatas(ancGclData);
+
+        this.ancGclDetail = new Ledger.pos({ id: 'id', ledgerId: 'ag_id', orderField: 'agd_order' });
+        const ancGclDetailData = await this.ctx.service.ancillaryGclDetail.getAllDataByCondition({ where: { tid } });
+        this.ancGclDetail.loadDatas(ancGclDetailData);
+
+        this.calcTemplate = await this.ctx.service.calcTmpl.getAllTemplateDetail(this.ctx.session.sessionProject.id, 'posCalc');
+    }
+    CheckPeg(text) {
+        const pegReg = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        return pegReg.test(text);
+    }
+    getPegNode(node) {
+        if (node) {
+            if (this.CheckPeg(node.name)) {
+                return node;
+            } else {
+                const parent = this.ledger.getParent(node);
+                return parent ? this.getPegNode(parent) : null;
+            }
+        }
+    }
+    getNodeByLevel(node, level) {
+        let cur = node;
+        while (cur && cur.level > level) {
+            cur = this.ledger.getParent(cur);
+        }
+        return cur;
+    }
+    getDwgc(peg, xmj) {
+        if (peg) {
+            return peg.name;
+        } else {
+            const node = this.getNodeByLevel(xmj, 2);
+            return node ? node.name : '';
+        }
+    }
+    getFbgc(peg, xmj) {
+        if (peg && peg.id !== xmj.id) {
+            const node = this.getNodeByLevel(xmj, peg.level + 1);
+            return node ? node.name : '';
+        } else {
+            const node = this.getNodeByLevel(xmj, 3);
+            return node ? node.name : '';
+        }
+    }
+    getFxgc (peg, xmj) {
+        if (!peg) {
+            const node = this.getNodeByLevel(xmj, 4);
+            return node ? node.name : '';
+        } else if (peg.id === xmj.id) {
+            if (xmj.level > 4) {
+                let value = '';
+                for (let level = 4; level < xmj.level; level++) {
+                    const node = this.getNodeByLevel(xmj, level);
+                    value = value === '' ? node.name : value + mergeChar + node.name;
+                }
+                return value;
+            } else {
+                return '';
+            }
+        } else {
+            if (peg.level + 2 < xmj.level) {
+                let value = '';
+                for (let level = peg.level + 2; level < xmj.level; level++) {
+                    const node = this.getNodeByLevel(xmj, level);
+                    value = value === '' ? node.name : value + mergeChar + node.name;
+                }
+                return value;
+            } else {
+                return '';
+            }
+        }
+    }
+    newCacheLeafXmj(leafXmj) {
+        const peg = this.getPegNode(leafXmj);
+        const cacheLX = {
+            id: leafXmj.id,
+            code: leafXmj.code,
+            jldy: leafXmj.name,
+            fbgc: this.getFbgc(peg, leafXmj),
+            fxgc: this.getFxgc(peg, leafXmj),
+            dwgc: this.getDwgc(peg, leafXmj),
+            drawing_code: leafXmj.drawing_code,
+        };
+        this.leafXmjs.push(cacheLX);
+        return cacheLX;
+    }
+    getCacheLeafXmj(leafXmj) {
+        if (!this.leafXmjs) this.leafXmjs = [];
+
+        const cacheLX = this.leafXmjs.find(lx => { return lx.id === leafXmj.id; });
+        if (!cacheLX) {
+            return this.newCacheLeafXmj(leafXmj);
+        } else {
+            return cacheLX;
+        }
+    }
+    convertCalcHeader(target, calcTemplate) {
+        for (const [i, col] of calcTemplate.col_set.entries()) {
+            target['detail_' + i] = col.title + (col.unit ? `(${col.unit})` : '');
+        }
+    }
+    convertCalcData(target, source, calcTemplate) {
+        for (const [i, col] of calcTemplate.col_set.entries()) {
+            target['detail_' + i] = source[col.field];
+        }
+    }
+    convertAncGclDetail(gclBaseData, pos, ancGcl, details, calcTemplate) {
+        const calc = [];
+        for (const d of details) {
+            calc.push(d.qty);
+            const dData = {
+                ...gclBaseData,
+                pos_id: pos.id, pos_name: pos.name,
+                anc_gcl_id: ancGcl.id, anc_gcl_name: ancGcl.name,
+                anc_gcl_detail_id: d.id, data_type: this.dataType.fs,
+            };
+            this.convertCalcData(dData, d, calcTemplate);
+            this.ledgerCheckData.push(dData);
+        }
+        return calc.join('+');
+    }
+    convertAncGcl(gclBaseData, pos, ancGcl) {
+        for (const ag of ancGcl) {
+            const agData = {
+                ...gclBaseData,
+                pos_id: pos.id, pos_name: pos.name,
+                anc_gcl_id: ag.id, anc_gcl_name: ancGcl.name, quantity: ag.quantity, data_type: this.dataType.fs,
+            };
+            const calcTemplate = this.calcTemplate.find(x => { return x.id === ag.calc_template; });
+            this.ledgerCheckData.push(agData);
+
+            const details = this.ancGclDetail.getLedgerPos(ag.id);
+            if (calcTemplate) this.convertCalcHeader(agData, calcTemplate);
+            if (calcTemplate && details && details.length > 0) {
+                agData.expr = this.convertAncGclDetail(gclBaseData, pos, ag, details, calcTemplate);
+            } else {
+                agData.expr = ag.expr;
+            }
+        }
+    }
+    convertPosCalcDetail(gclBaseData, pos, details, calcTemplate) {
+        const calc = [];
+        for (const d of details) {
+            calc.push(d.qty);
+            const dData = {
+                ...gclBaseData,
+                pos_id: pos.id, pos_name: pos.name,
+                pos_calc_detail_id: d.id, data_type: this.dataType.jl,
+            };
+            this.convertCalcData(dData, d, calcTemplate);
+            this.ledgerCheckData.push(dData);
+        }
+        return calc.join('+');
+    }
+    convertPosRange(gclBaseData, posRange, calcTemplate) {
+        const calc = [];
+        for (const p of posRange) {
+            calc.push(p.sgfh_qty);
+            const posData = {
+                ...gclBaseData,
+                pos_id: p.id, pos_name: p.name, quantity: p.sgfh_qty, data_type: this.dataType.jl,
+            };
+            this.ledgerCheckData.push(posData);
+            const details = this.posCalcDetail.getLedgerPos(p.id);
+            if (calcTemplate) this.convertCalcHeader(posData, calcTemplate);
+            if (calcTemplate && details && details.length > 0) {
+                posData.expr = this.convertPosCalcDetail(gclBaseData, p, details, calcTemplate);
+            } else {
+                posData.expr = p.sgfh_expr;
+            }
+            const ancGcl = this.ancGcl.getLedgerPos(p.id);
+            if (ancGcl && ancGcl.length > 0) this.convertAncGcl(gclBaseData, p, ancGcl);
+        }
+        return calc.join('+');
+    }
+    convertGclNode(node, leafXmj) {
+        const cacheLeafXmj = this.getCacheLeafXmj(leafXmj);
+        const posRange = this.pos.getLedgerPos(node.id);
+        const gclBaseData = {
+            id: node.id, tid: node.tender_id, leaf_xmj_id: leafXmj.id,
+            b_code: node.b_code, name: node.name, unit: node.unit, unit_price: node.unit_price,
+            drawing_code: node.drawing_code,
+            g_jldy: cacheLeafXmj.jldy, g_fbgc: cacheLeafXmj.fbgc, g_fxgc: cacheLeafXmj.fxgc, g_dwgc: cacheLeafXmj.dwgc, g_bwmx: cacheLeafXmj.bwmx,
+        };
+        const gclData = { ...gclBaseData, quantity: node.sgfh_qty, data_type: this.dataType.jl } ;
+        this.ledgerCheckData.push(gclData);
+        if (posRange && posRange.length > 0) {
+            const calcTemplate = node.calc_template ? this.calcTemplate.find(x => { return x.id === node.calc_template; }) : null;
+            gclData.expr = this.convertPosRange(gclBaseData, posRange, calcTemplate);
+        } else {
+            gclData.expr = node.sgfh_expr;
+        }
+    }
+    recursiveConvertLedgerCheckData(nodes, leafXmj) {
+        for (const node of nodes) {
+            if (node.b_code) {
+                if (node.children.length > 0) {
+                    this.recursiveConvertLedgerCheckData(node.children, leafXmj);
+                } else {
+                    this.convertGclNode(node, leafXmj);
+                }
+            } else if (node.children.length > 0) {
+                this.recursiveConvertLedgerCheckData(node.children, node);
+            }
+        }
+    }
+    async getLedgerCheckData(tid) {
+        await this.ctx.service.tender.checkTender(tid);
+        await this.loadLedgerData(tid);
+        this.ledgerCheckData = [];
+        this.recursiveConvertLedgerCheckData(this.ledger.children, null);
+        return this.ledgerCheckData;
+    }
+}
+
 module.exports = {
     jhHelper,
     fjHelper,
+    huajingHelper,
 };

BIN=BIN
app/public/images/juecedaping08.png


+ 17 - 8
app/public/js/budget_compare.js

@@ -59,8 +59,8 @@ $(document).ready(() => {
         { key: 'zb', name: '招标预算', dgn1: 'zb_dgn_qty1', dgn2: 'zb_dgn_qty2', tp: 'zb_tp'},
         { key: 'ctrl', name: '控制目标', dgn1: 'ctrl_dgn_qty1', dgn2: 'ctrl_dgn_qty2', tp: 'ctrl_tp'},
         { key: 'real', name: '实际投资', dgn1: 'real_dgn_qty1', dgn2: 'real_dgn_qty2', tp: 'real_tp'},
-        { key: 'tz', name: '台账', dgn1: 'dgn_qty1', dgn2: 'dgn_qty2', tp: 'total_price'},
-        { key: 'final', name: '预估决算', dgn1: 'final_dgn_qty1', dgn2: 'final_dgn_qty2', tp: 'final_tp'},
+        { key: 'tz', name: '台账/合同', dgn1: 'dgn_qty1', dgn2: 'dgn_qty2', tp: 'total_price'},
+        { key: 'final', name: '计量/支付', dgn1: 'final_dgn_qty1', dgn2: 'final_dgn_qty2', tp: 'final_tp'},
     ];
     const spreadSetting = {
         cols: [
@@ -131,7 +131,7 @@ $(document).ready(() => {
             if (!phase2) phase2 = this.comparePhase2;
             const cp1 = comparePhase.find(x => { return x.key === phase1; });
             const cp2 = comparePhase.find(x => { return x.key === phase2; });
-            return `增减幅度=(${cp2.name}-${cp1.name})/${cp1.name}*100%`;
+            return `增减幅度=(${cp2.name}-${cp1.name})÷${cp1.name}*100%`;
         },
         setComparePhase(phase1, phase2) {
             compareObj.comparePhase1 = phase1;
@@ -340,7 +340,7 @@ $(document).ready(() => {
             this.calcStackedBar(finalTree);
             this.reCalculateCompareData(finalTree);
             SpreadJsObj.loadSheetData(compareSheet, SpreadJsObj.DataType.Tree, finalTree);
-            if (sfSelect) sfSelect.reloadSelect(this.finalInfo.tender);
+            if (sfSelect) sfSelect.reloadSelect(this.finalInfo.tender, this.finalInfo.contract);
         },
         reCalculateCompareData(calcTree) {
             const tree = calcTree || compareSheet.zh_tree;
@@ -512,13 +512,19 @@ $(document).ready(() => {
 
             this.contractTree = createNewPathTree('base', { id: 'contract_id', pid: 'contract_pid', order: 'order', level: 'level', fullPath: 'full_path', rootId: -1 });
             this.contractTree.loadDatas(contract);
+            if (compareObj.finalInfo) {
+                this.contractTree.datas.forEach(x => {
+                    x.selected = compareObj.finalInfo.contract.indexOf(x.id) >= 0;
+                });
+            }
             this.contractSpread = SpreadJsObj.createNewSpread($('#sf-contract-spread')[0]);
             this.contractSheet = this.contractSpread.getActiveSheet();
             SpreadJsObj.initSheet(this.contractSheet, {
                 cols: [
                     {title: '选择', colSpan: '1', rowSpan: '1', field: 'selected', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox'},
-                    {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 120, formatter: '@', cellType: 'tree'},
-                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 200, formatter: '@'},
+                    {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 90, formatter: '@', cellType: 'tree'},
+                    {title: '合同编号', colSpan: '1', rowSpan: '1', field: 'c_code', hAlign: 0, width: 90, formatter: '@'},
+                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 180, formatter: '@'},
                 ],
                 emptyRows: 0,
                 headRows: 1,
@@ -599,11 +605,14 @@ $(document).ready(() => {
                 });
             });
         }
-        reloadSelect(select) {
+        reloadSelect(select, contract) {
             if (compareObj.finalInfo) {
                 this.selectTree.datas.forEach(x => {
                     x.selected = select.indexOf(x.tid + '') >= 0;
-                })
+                });
+                this.contractTree.datas.forEach(x => {
+                    x.selected = contract.indexOf(x.id) >= 0;
+                });
             }
             SpreadJsObj.reloadColData(this.sheet, 0);
         }

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

@@ -79,7 +79,7 @@ $(document).ready(() => {
                     this.tree.nodes.forEach(x => { x[col.field] = 0; });
                 }
                 for (const s of gp.stages) {
-                    for (b of s.detail.bills) {
+                    for (const 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) {
@@ -183,4 +183,4 @@ $(document).ready(() => {
             ledgerGatherSpread.refresh();
         }
     });
-});
+});

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

@@ -255,6 +255,7 @@ $(document).ready(function() {
 
             switch (col.field) {
                 case 'in_tp':
+                case 'in_excl_tax_tp':
                     info.cancel = (node.children && node.children.length > 0) || node.had_detail;
                     if (!info.cancel) {
                         const detailRange = detailObj.data.getPartData(node.id);
@@ -828,7 +829,7 @@ $(document).ready(function() {
                 if (result.calc) {
                     const refreshNode = billsObj.tree.loadPostData({ update: result.bills });
                     billsObj.refreshTree(refreshNode);
-                    detailObj.data.updateDatas(result.detail);
+                    detailObj.data.updateDatas({ update: result.detail });
                     detailObj.reloadDetailData();
                 }
                 $('#cost-tp').attr('org', decimal.tp);

+ 550 - 0
app/public/js/cost_stage_duty.js

@@ -0,0 +1,550 @@
+function transExpr(expr) {
+    return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
+}
+
+$(document).ready(function() {
+    autoFlashHeight();
+    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: [
+                    'total_price',
+                ],
+                keys: ['id', 'stage_id', 'tree_id'],
+            };
+            this.tree = createNewPathTree('ledger', this.treeSetting);
+            this.spreadSetting = {
+                cols: [
+                    {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 180, formatter: '@', cellType: 'tree'},
+                    {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 120, formatter: '@'},
+                    {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+                    {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit', comboEdit: true},
+                    {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 100, formatter: '@'},
+                    {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@'},
+                ],
+                emptyRows: 0,
+                headRows: 2,
+                headRowHeight: [25, 25],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                readOnly: readOnly,
+                localCache: {
+                    key: 'cost_duty',
+                    colWidth: true,
+                },
+                headColWidth: [50],
+                tipsSum: true,
+            };
+            sjsSettingObj.setFxTreeStyle(this.spreadSetting, sjsSettingObj.FxTreeStyle.jz);
+
+            this.ckBillsSpread = window.location.pathname + '-billsSelect';
+
+            this.initSpread();
+            this.initOtherEvent();
+        }
+        initSpread() {
+            SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
+            this.spread.bind(spreadNS.Events.SelectionChanged, this.selectionChanged);
+            this.spread.bind(spreadNS.Events.topRowChanged, this.topRowChanged);
+            this.spread.bind(spreadNS.Events.ClipboardChanging, function (e, info) {
+                const copyText = SpreadJsObj.getFilterCopyText(info.sheet);
+                SpreadJsObj.Clipboard.setCopyData(copyText);
+            });
+            if (readOnly) return;
+            this.spread.bind(spreadNS.Events.EditEnded, this.editEnded);
+            this.spread.bind(spreadNS.Events.EditStarting, this.editStarting);
+            this.spread.bind(spreadNS.Events.ClipboardPasting, this.clipboardPasting);
+            SpreadJsObj.addDeleteBind(this.spread, this.deletePress);
+        }
+        initOtherEvent() {
+            const self = this;
+            // 增删上下移升降级
+            $('a[name="base-opr"]').click(function () {
+                self.baseOpr(this.getAttribute('type'));
+            });
+            $('body').on('change', 'input[name=in_tp]', function() {
+                const data = {
+                    in_tp: parseFloat($('input[name=in_tp]').val()),
+                };
+                postData('update', { target: 'extra', data} , function(result) {
+                    billsObj.setExtra(result);
+                });
+            });
+        }
+        refreshOperationValid() {
+            const setObjEnable = function (obj, enable) {
+                if (enable) {
+                    obj.removeClass('disabled');
+                } else {
+                    obj.addClass('disabled');
+                }
+            };
+            const invalidAll = function () {
+                setObjEnable($('a[name=base-opr][type=add]'), false);
+                setObjEnable($('a[name=base-opr][type=delete]'), false);
+                setObjEnable($('a[name=base-opr][type=up-move]'), false);
+                setObjEnable($('a[name=base-opr][type=down-move]'), false);
+                setObjEnable($('a[name=base-opr][type=up-level]'), false);
+                setObjEnable($('a[name=base-opr][type=down-level]'), false);
+            };
+            const sel = this.sheet.getSelections()[0];
+            const row = sel ? sel.row : -1;
+            const tree = this.sheet.zh_tree;
+            if (!tree) {
+                invalidAll();
+                return;
+            }
+            const first = tree.nodes[row];
+            if (!first) {
+                invalidAll();
+                return;
+            }
+            let last = first, sameParent = true;
+            if (sel.rowCount > 1 && first) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = tree.nodes[sel.row + r];
+                    if (!rNode) {
+                        sameParent = false;
+                        break;
+                    }
+                    if (rNode.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)) {
+                        sameParent = false;
+                        break;
+                    }
+                    last = rNode;
+                }
+            }
+            const preNode = tree.getPreSiblingNode(first);
+            const valid = !this.sheet.zh_setting.readOnly;
+
+            setObjEnable($('a[name=base-opr][type=add]'), valid && first && first.tree_level > 1);
+            setObjEnable($('a[name=base-opr][type=delete]'), valid && first && first.tree_level > 1 && sameParent);
+            setObjEnable($('a[name=base-opr][type=up-move]'), valid && first && first.tree_level > 1 && sameParent && preNode);
+            setObjEnable($('a[name=base-opr][type=down-move]'), valid && first && first.tree_level > 1 && sameParent  && !tree.isLastSibling(last));
+            setObjEnable($('a[name=base-opr][type=up-level]'), valid && first && first.tree_level > 2 && sameParent && tree.getParent(first) && first.tree_level > 1);
+            setObjEnable($('a[name=base-opr][type=down-level]'), valid && first && first.tree_level > 1 && sameParent && preNode);
+        }
+        refreshTree(data) {
+            const sheet = this.sheet;
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    data.delete.sort(function (a, b) {
+                        return b.deleteIndex - a.deleteIndex;
+                    });
+                    for (const d of data.delete) {
+                        sheet.deleteRows(d.deleteIndex, 1);
+                    }
+                }
+                // 处理新增
+                if (data.create) {
+                    const newNodes = data.create;
+                    if (newNodes) {
+                        newNodes.sort(function (a, b) {
+                            return a.index - b.index;
+                        });
+
+                        for (const node of newNodes) {
+                            sheet.addRows(node.index, 1);
+                            SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
+                        }
+                    }
+                }
+                // 处理更新
+                if (data.update) {
+                    const rows = [];
+                    for (const u of data.update) {
+                        rows.push(tree.nodes.indexOf(u));
+                    }
+                    SpreadJsObj.reLoadRowsData(sheet, rows);
+                }
+                // 处理展开
+                if (data.expand) {
+                    const expanded = [];
+                    for (const e of data.expand) {
+                        if (expanded.indexOf(e) === -1) {
+                            const posterity = tree.getPosterity(e);
+                            for (const p of posterity) {
+                                sheet.setRowVisible(tree.nodes.indexOf(p), p.visible);
+                                expanded.push(p);
+                            }
+                        }
+                    }
+                }
+            });
+            this.refreshExtraTable();
+        }
+        loadData(datas) {
+            this.tree.loadDatas(datas);
+            treeCalc.calculateAll(this.tree);
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.tree);
+            SpreadJsObj.loadTopAndSelect(this.sheet, this.ckBillsSpread);
+            this.refreshOperationValid();
+        }
+        getDefaultSelectInfo() {
+            if (!this.tree) return;
+            const sel = this.sheet.getSelections()[0];
+            const node = this.sheet.zh_tree.nodes[sel.row];
+            if (!node) return;
+            let count = 1;
+            if (sel.rowCount > 1) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = this.sheet.zh_tree.nodes[sel.row + r];
+                    if (rNode.tree_level > node.tree_level) continue;
+                    if ((rNode.tree_level < node.tree_level) || (rNode.tree_level === node.tree_level && rNode.tree_pid !== node.tree_pid)) {
+                        toastr.warning('请选择同一节点下的节点,进行该操作');
+                        return;
+                    }
+                    count += 1;
+                }
+            }
+            return [this.tree, node, count];
+        }
+        baseOpr(type, addCount = 1) {
+            const self = this;
+            const sheet = self.sheet;
+            const sel = sheet.getSelections()[0];
+            const [tree, node, count] = this.getDefaultSelectInfo();
+            if (!tree || !node || !count) return;
+
+            const updateData = {
+                target: 'ledger', postType: type,
+                postData: {
+                    id: node.tree_id,
+                    count: type === 'add' ? addCount : count,
+                }
+            };
+            if (type === 'delete') {
+                deleteAfterHint(function () {
+                    postData('update', updateData, function (result) {
+                        const refreshData = tree.loadPostData(result);
+                        self.refreshTree(refreshData);
+                        if (sel) {
+                            sheet.setSelection(sel.row, sel.col, 1, sel.colCount);
+                        }
+                        self.refreshOperationValid();
+                    });
+                });
+            } else {
+                postData('update', updateData, function (result) {
+                    const refreshData = tree.loadPostData(result);
+                    self.refreshTree(refreshData);
+                    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)]);
+                        }
+                    } else if (type === 'add') {
+                        const sel = sheet.getSelections()[0];
+                        if (sel) {
+                            sheet.setSelection(tree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
+                            SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(refreshData.create[0])]);
+                        }
+                    }
+                    self.refreshOperationValid();
+                });
+            }
+        }
+        // 事件
+        selectionChanged(e, info) {
+            if (info.newSelections) {
+                if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                    billsObj.refreshOperationValid();
+                }
+            }
+        }
+        topRowChanged(e, info) {
+            SpreadJsObj.saveTopAndSelect(info.sheet, billsObj.ckBillsSpread);
+        }
+        editStarting(e, info) {
+            if (!info.sheet.zh_setting || !info.sheet.zh_tree) return;
+
+            const col = info.sheet.zh_setting.cols[info.col];
+            const node = info.sheet.zh_tree.nodes[info.row];
+            if (!node) {
+                info.cancel = true;
+                return;
+            }
+
+            switch (col.field) {
+                case 'quantity':
+                case 'unit_price':
+                    info.cancel = (node.children && node.children.length > 0);
+                    break;
+            }
+        }
+        editEnded(e, info) {
+            if (!info.sheet.zh_setting) return;
+
+            const node = SpreadJsObj.getSelectObject(info.sheet);
+            const data = { id: node.id, stage_id: node.stage_id, tree_id: node.tree_id };
+            // 未改变值则不提交
+            const col = info.sheet.zh_setting.cols[info.col];
+            const orgValue = node[col.field];
+            const newValue = trimInvalidChar(info.editingText);
+            if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (newValue === ''))) return;
+
+            if (info.editingText) {
+                const text = newValue;
+                if (col.type === 'Number') {
+                    const num = _.toNumber(text);
+                    if (_.isFinite(num)) {
+                        data[col.field] = num;
+                    } else {
+                        try {
+                            data[col.field] = ZhCalc.mathCalcExpr(transExpr(text));
+                        } catch(err) {
+                            toastr.error('输入的表达式非法');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                    }
+                } else {
+                    data[col.field] = text;
+                }
+            } else {
+                data[col.field] = col.type === 'Number' ? 0 : '';
+            }
+            // 更新至服务器
+            postData('update', {target: 'ledger', postType: 'update', postData: data}, function (result) {
+                const refreshNode = billsObj.tree.loadPostData(result);
+                billsObj.refreshTree(refreshNode);
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row, 1);
+            });
+        }
+        deletePress (sheet) {
+            if (!sheet.zh_setting) return;
+            const sel = sheet.getSelections()[0], datas = [];
+            for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                let bDel = false;
+                const node = sheet.zh_tree.nodes[iRow];
+                const data = sheet.zh_tree.getNodeKeyData(node);
+                for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                    const col = sheet.zh_setting.cols[iCol];
+                    const style = sheet.getStyle(iRow, iCol);
+                    if (style.locked) continue;
+
+                    data[col.field] = col.type === 'Number' ? 0 : '';
+                    bDel = true;
+                }
+                if (bDel) datas.push(data);
+            }
+            if (datas.length > 0) {
+                postData('update', {target: 'ledger', postType: 'update', postData: datas}, function (result) {
+                    const refreshNode = sheet.zh_tree.loadPostData(result);
+                    billsObj.refreshTree(refreshNode);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(sheet, sel.row, sel.rowCount);
+                });
+            }
+        }
+        clipboardPasting(e, info) {
+            info.cancel = true;
+            const tree = info.sheet.zh_tree, setting = info.sheet.zh_setting;
+            if (!setting || !tree) return;
+
+            const pasteData = info.pasteData.html
+                ? SpreadJsObj.analysisPasteHtml(info.pasteData.html)
+                : (info.pasteData.text === ''
+                    ? SpreadJsObj.Clipboard.getAnalysisPasteText()
+                    : SpreadJsObj.analysisPasteText(info.pasteData.text));
+            const datas = [], filterNodes = [];
+
+            let level, filterRow = 0;
+            for (let iRow = 0; iRow < info.cellRange.rowCount; iRow ++) {
+                const curRow = info.cellRange.row + iRow;
+                const node = tree.nodes[curRow];
+                if (!node) continue;
+
+                if (!level) level = node.level;
+                if (node.level < level) break;
+
+                let bPaste = false;
+                const data = info.sheet.zh_tree.getNodeKeyData(node);
+                for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                    const curCol = info.cellRange.col + iCol;
+                    const colSetting = info.sheet.zh_setting.cols[curCol];
+                    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) {
+                    datas.push(data);
+                } else {
+                    filterNodes.push(node);
+                }
+            }
+            if (datas.length > 0) {
+                postData('update', {target: 'ledger', postType: 'update', postData: datas}, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    if (refreshNode.update) refreshNode.update = refreshNode.update.concat(filterNodes);
+                    billsObj.refreshTree(refreshNode);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                });
+            } else {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+            }
+        }
+        // 附加信息
+        getSum() {
+            return ZhCalc.sum(this.tree.children.map(x => { return x.total_price; }));
+        }
+        refreshExtraTable() {
+            const html = [];
+            const in_tp = this.extra.in_tp || 0;
+            const out_tp = this.getSum();
+            const profit = ZhCalc.sub(in_tp, out_tp);
+            const profit_rate = in_tp ? ZhCalc.mul(ZhCalc.div(profit, in_tp), 100, 2) : 0;
+            html.push(`<tr><td>项目收入</td><td><input type="number" class="form-control form-control-sm nospin" name="in_tp" value="${in_tp}" ${readOnly ? 'readOnly' : ''}></td></tr>`);
+            html.push(`<tr><td>项目支出</td><td>${out_tp || 0}</td></tr>`);
+            html.push(`<tr><td>利润</td><td>${profit}</td></tr>`);
+            html.push(`<tr><td>利润率</td><td>${profit_rate}</td></tr>`);
+            $('#duty-extra').html(html.join(''));
+        }
+        setExtra(extra) {
+            this.extra = extra;
+            this.refreshExtraTable();
+        }
+    }
+    const billsObj = new BillsObj();
+
+    const searchObj = $.billsSearch({
+        selector: '#search',
+        searchRangeStr: '项目节编号/清单编号/名称',
+        searchSpread: billsObj.spread,
+        keyId: 'tree_id',
+        resultSpreadSetting: {
+            cols: [
+                {title: '项目节编号', field: 'code', hAlign: 0, width: 120, formatter: '@'},
+                {title: '清单编号', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+                {title: '名称', field: 'name', width: 150, hAlign: 0, formatter: '@'},
+                {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@'},
+            ],
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            headColWidth: [30],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            selectedBackColor: '#fffacd',
+            readOnly: true,
+        },
+    });
+    // 加载数据
+    postData('load', { filter: 'bills;extra' }, function(result) {
+        billsObj.loadData(result.bills);
+        billsObj.setExtra(result.extra);
+    });
+
+    // 展开收起标准清单
+    $('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();
+    });
+
+    // 工具栏spr
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            billsObj.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();
+        }
+    });
+    // 显示层次
+    (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);
+});

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

@@ -1291,7 +1291,7 @@ $(document).ready(function() {
                 if (result.calc) {
                     const refreshNode = billsObj.tree.loadPostData({ update: result.bills });
                     billsObj.refreshTree(refreshNode);
-                    detailObj.data.updateDatas(result.detail);
+                    detailObj.data.updateDatas({ update: result.detail });
                     detailObj.reloadDetailData();
                 }
                 $('#cost-tp').attr('org', decimal.tp);

+ 1 - 1
app/router.js

@@ -576,7 +576,7 @@ module.exports = app => {
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/file/upload', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.uploadStageFile');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/file/delete', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.deleteStageFile');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/stage/tag', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.tag');
-    app.get('/sp/:id/cost/tender/:tid/flow', sessionAuth, subProjectCheck, tenderCheck, projectManagerCheck, 'tenderController.shenpiSet');
+    app.get('/sp/:id/cost/tender/:tid/flow', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, projectManagerCheck, 'tenderController.shenpiSet');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/audit/add', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.addStageAudit');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/audit/delete', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.deleteStageAudit');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/audit/start', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.stageAuditStart');

+ 1 - 1
app/service/budget.js

@@ -286,7 +286,7 @@ module.exports = app => {
             try {
                 await conn.update(this.tableName, {id: budget.id, final_id: final.id, final_time: new Date() });
                 await conn.insert(this.ctx.service.budgetFinal.tableName, finalData);
-                await conn.update(this.ctx.service.budgetFinalList.tableName, { id: final.id, tender_info: JSON.stringify(final.tender_info), status: 3});
+                await conn.update(this.ctx.service.budgetFinalList.tableName, { id: final.id, tender_info: JSON.stringify(final.tender_info), contract: contract.join(','), status: 3});
                 await conn.commit();
                 return finalData;
             } catch (err) {

+ 1 - 0
app/service/budget_final_list.js

@@ -33,6 +33,7 @@ module.exports = app => {
         _analysisFinal(data) {
             data.tender = data.tender ? data.tender.split(',') : [];
             data.tender_info = data.tender_info ? JSON.parse(data.tender_info) : [];
+            data.contract = data.contract ? data.contract.split(',') : [];
         }
 
         async getFinal(id) {

+ 1 - 1
app/service/contract.js

@@ -62,7 +62,7 @@ module.exports = app => {
                     // party_b: data.party_b,
                     // sign_date: data.sign_date,
                     // remark: data.remark,
-                    // type: data.type,
+                    type: data.type,
                     create_time: new Date(),
                     need_shenpi: options.spid ? this.ctx.subProject.page_show.openContractSubProjectShenpi : this.ctx.subProject.page_show.openContractTenderShenpi,
                 };

+ 21 - 7
app/service/cost_stage.js

@@ -25,13 +25,14 @@ module.exports = app => {
             super(ctx);
             this.tableName = 'cost_stage';
             this.stageType = {
+                duty: { key: 'duty', name: '责任成本', decimal: { tp: 6, tax: 2, excl_tax_tp: 6 }, shenpi_status: 'cost_stage_duty', push_type: 'costStageDuty', dataService: 'costStageDuty' },
                 ledger: { key: 'ledger', name: '成本报审', decimal: { tp: 6, tax: 2, excl_tax_tp: 6 }, shenpi_status: 'cost_stage_ledger', push_type: 'costStageLedger', dataService: 'costStageLedger', detailService: 'costStageDetail' },
                 book: { key: 'book', name: '财务账面', decimal: { tp: 6, tax: 2, excl_tax_tp: 6 }, shenpi_status: 'cost_stage_book', push_type: 'costStageBook', dataService: 'costStageBook', detailService: 'costStageBookDetail' },
                 analysis: { key: 'analysis', name: '成本分析', decimal: { tp: 6, tax: 2, excl_tax_tp: 6 }, shenpi_status: 'cost_stage_analysis', push_type: 'costStageAnalysis', dataService: 'costStageAnalysis', detailService: 'costStageAnalysisDetail' },
             };
         }
 
-        _analysisstage(stage) {
+        _analysisStage(stage) {
             if (!stage) return;
             const stages = stage instanceof Array ? stage : [stage];
             if (stage.length === 0) return;
@@ -51,9 +52,10 @@ module.exports = app => {
                 }
                 s.typeInfo = this.stageType[s.stage_type];
                 s.rela_stage = s.rela_stage ? JSON.parse(s.rela_stage) : null;
+                s.extra_info = s.extra_info ? JSON.parse(s.extra_info) : {};
             });
         }
-        async calcRealtime(stage) {
+        async calcRuntime(stage) {
             const typeInfo = this.stageType[stage.stage_type];
             if (!typeInfo) return;
 
@@ -71,24 +73,24 @@ module.exports = app => {
             const sql = `SELECT cls.*, pa.name AS user_name FROM ${this.tableName} cls LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON cls.create_user_id = pa.id` +
                         `  WHERE cls.tid = ? and cls.stage_type = ? ORDER BY cls.stage_order ${sort}`;
             const result = await this.db.query(sql, [tid, stage_type]);
-            this._analysisstage(result);
+            this._analysisStage(result);
             return result;
         }
         async getAllCheckedStages(tid, stage_type, sort = 'ASC') {
             const sql = `SELECT cls.*, pa.name AS user_name FROM ${this.tableName} cls LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON cls.create_user_id = pa.id` +
                 `  WHERE cls.tid = ? AND cls.stage_type = ? AND audit_status = ? ORDER BY cls.stage_order ${sort}`;
             const result = await this.db.query(sql, [tid, stage_type, audit.status.checked]);
-            this._analysisstage(result);
+            this._analysisStage(result);
             return result;
         }
         async getStage(id) {
             const result = await this.getDataById(id);
-            this._analysisstage(result);
+            this._analysisStage(result);
             return result;
         }
         async getStageByOrder(tid, stage_type, stage_order) {
             const result = await this.getDataByCondition({ tid, stage_type, stage_order });
-            this._analysisstage(result);
+            this._analysisStage(result);
             return result;
         }
 
@@ -117,6 +119,7 @@ module.exports = app => {
             const preStage = maxOrder > 0 ? await this.getStageByOrder(tid, stage_type, maxOrder) : null;
             if (preStage) {
                 data.stage_pre_tp = JSON.stringify(preStage.stage_end_tp);
+                data.decimal = JSON.stringify(preStage.decimal);
             }
             const transaction = await this.db.beginTransaction();
             try {
@@ -294,6 +297,7 @@ module.exports = app => {
             const info = this.ctx.tender.info;
             const typeInfo = this.stageType[stage.stage_type];
             const shenpi_status = info.shenpi[typeInfo.shenpi_status];
+            console.log(typeInfo.shenpi_status, info.shenpi);
             if ((stage.audit_status === status.uncheck || stage.audit_status === status.checkNo) && shenpi_status !== shenpiConst.sp_status.sqspr) {
                 // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
                 const auditList = await this.ctx.service.costStageAudit.getAllDataByCondition({ where: { stage_id: stage.id, audit_times: stage.audit_times }, orders: [['audit_order', 'asc']] });
@@ -336,7 +340,7 @@ module.exports = app => {
                 orders: [['stage_order', 'desc']],
             });
             if (lastCheckedStage && lastCheckedStage.length > 0) {
-                this._analysisstage(lastCheckedStage[0]);
+                this._analysisStage(lastCheckedStage[0]);
                 return lastCheckedStage[0];
             }
             return null;
@@ -346,6 +350,16 @@ module.exports = app => {
             const num = await this.db.queryOne(`SELECT COUNT(*) as num From ${this.tableName} WHERE tid = ? and stage_type = ? and audit_status = ?`, [tenderId, stage_type, audit.status.checked]);
             return num ? num.num : 0;
         }
+
+        async saveExtraInfo(stage, data) {
+            const extra_info = this.ctx.helper._.assign(stage.extra_info, data);
+            try {
+                await this.defaultUpdate({ id: stage.id, extra_info: JSON.stringify(extra_info) });
+                return extra_info;
+            } catch (err) {
+                throw '保存附加信息错误';
+            }
+        }
     }
 
     return CostStage;

+ 4 - 4
app/service/cost_stage_book.js

@@ -216,9 +216,9 @@ module.exports = app => {
                 orgDetail = await this.ctx.service.costStageDetail.getAllDataByCondition({ where: { stage_id: this.ctx.costStage.relaStage.id }});
                 for (const cd of detailCalcData) {
                     const nd = { id: cd.id, ledger_id: cd.ledger_id, detail_id: cd.detail_id };
-                    const cl = orgLedger.find(x => { return x.id === cd.ledger_id });
+                    const od = orgDetail.find(x => { return x.id === cd.detail_id });
                     nd.in_tp = this.ctx.helper.round(cd.in_tp, decimal.tp);
-                    const divNum = cl.tax ? this.ctx.helper.add(1, this.ctx.helper.div(cl.tax, 100)) : 1;
+                    const divNum = od.tax ? this.ctx.helper.add(1, this.ctx.helper.div(od.tax, 100)) : 1;
                     nd.in_excl_tax_tp = this.ctx.helper.div(nd.in_tp !== undefined ? nd.in_tp : nd.in_tp, divNum, decimal.excl_tax_tp);
                     detailUpdateData.push(nd);
                 }
@@ -262,8 +262,8 @@ module.exports = app => {
             detailUpdateData.forEach(u => {
                 u.book_id = u.id;
                 delete u.id;
-                const cl = orgDetail.find(x => { return x.id === od.detail_id; });
-                i.id = cl.id;
+                const cl = orgDetail.find(x => { return x.id === u.detail_id; });
+                u.id = cl.id;
             });
             return { calc: calcTp, bills: updateData, detail: detailUpdateData };
         }

+ 403 - 0
app/service/cost_stage_duty.js

@@ -0,0 +1,403 @@
+'use strict';
+
+/**
+ *
+ * 支付审批-安全生产
+ * @author Mai
+ * @date
+ * @version
+ */
+const billsUtils = require('../lib/bills_utils');
+const costFields = {
+    textFields: ['code', 'b_code', 'name', 'unit', 'memo', 'drawing_code'],
+    curFields: ['quantity', 'unit_price', 'total_price'],
+    readFields: ['read_quantity', 'read_unit_price', 'read_total_price'],
+    treeFields: ['tree_id', 'tree_pid', 'tree_level', 'tree_order', 'tree_full_path', 'tree_is_leaf'],
+    baseFields: ['id', 'cost_id', 'tender_id', 'stage_id',],
+};
+costFields.calcFields = [...costFields.curFields];
+costFields.editQueryFields = [...costFields.baseFields, ...costFields.treeFields, ...costFields.textFields, ...costFields.curFields];
+costFields.readQueryFields = [...costFields.baseFields, ...costFields.treeFields, ...costFields.textFields, ...costFields.readFields];
+costFields.compareQueryFields = [...costFields.baseFields, ...costFields.treeFields, ...costFields.textFields, ...costFields.curFields, 'calc_his'];
+
+const auditConst = require('../const/audit').costStage;
+
+module.exports = app => {
+
+    class CostStageLedger extends app.BaseTreeService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, {
+                mid: 'stage_id',
+                kid: 'tree_id',
+                pid: 'tree_pid',
+                order: 'tree_order',
+                level: 'tree_level',
+                isLeaf: 'tree_is_leaf',
+                fullPath: 'tree_full_path',
+                keyPre: 'cost_duty_maxTreeId:',
+                uuid: true,
+            });
+            this.tableName = 'cost_stage_duty';
+            this.calcFields = costFields.calcFields;
+        }
+
+        // 继承方法
+        clearParentingData(data) {
+            for (const f of costFields.calcFields) {
+                data[f] = 0;
+            }
+        }
+
+        _getDefaultData(data, stage) {
+            data.id = this.uuid.v4();
+            data.cost_id = data.id;
+            data.tender_id = stage.tid;
+            data.stage_id = stage.id;
+            data.add_user_id = this.ctx.session.sessionUser.accountId;
+            data.update_user_id = data.add_user_id;
+        }
+
+        async getEditData(stage) {
+            const result = await this.getAllDataByCondition({
+                where: { stage_id: stage.id },
+                columns: costFields.editQueryFields
+            });
+            return result;
+        }
+        async getReadData(stage) {
+            const result = await this.getAllDataByCondition({
+                where: { stage_id: stage.id },
+                columns: costFields.readQueryFields
+            });
+            result.forEach(x => {
+                for(const prop of costFields.curFields) {
+                    x[prop] = x['read_' + prop];
+                }
+            });
+            return result;
+        }
+        async getCompareData(stage) {
+            const result = await this.getAllDataByCondition({
+                where: { stage_id: stage.id },
+                columns: costFields.compareQueryFields
+            });
+            result.forEach(x => {
+                x.calc_his = x.calc_his ? JSON.parse(x.calc_his) : [];
+            });
+            return result;
+        }
+
+        async init(stage, transaction) {
+            if (!stage || !transaction) throw '成本报审数据错误';
+
+            if (!this.ctx.tender.data.measure_type) throw '未设置台账模板,请联系管理员设置';
+            const templateId = await this.ctx.service.valuation.getValuationTemplate(
+                this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type);
+            const tempData = await this.ctx.service.tenderNodeTemplate.getData(templateId, true);
+            if (tempData.length === 0) throw '台账模板无数据,连请联系管理员修改';
+
+            const insertData = [];
+            for (const b of tempData) {
+                const bills = {
+                    tree_id: b.template_id, tree_pid: b.pid, tree_order: b.order, tree_level: b.level, tree_full_path: b.full_path, tree_is_leaf: b.is_leaf,
+                    code: b.code || '', b_code: '', name: b.name || '', unit: b.unit || '',
+                };
+                this._getDefaultData(bills, stage);
+                insertData.push(bills);
+            }
+
+            const operate = await transaction.insert(this.tableName, insertData);
+            return operate.affectedRows === insertData.length;
+        }
+        async initStageData(transaction, stage, preStage) {
+            await this.init(stage, transaction);
+        }
+
+        /**
+         * 新增数据(供内部或其他service类调用, controller不可直接使用)
+         * @param {Array|Object} data - 新增数据
+         * @param {Number} stageId - 期id
+         * @param {Object} transaction - 新增事务
+         * @return {Promise<boolean>} - {Promise<是否正确新增成功>}
+         */
+        async innerAdd(data, stageId, transaction) {
+            const datas = data instanceof Array ? data : [data];
+            if (!stageId) {
+                throw '期id错误';
+            }
+            if (datas.length <= 0) {
+                throw '插入数据为空';
+            }
+            if (!transaction) {
+                throw '内部错误';
+            }
+            // 整理数据
+            const insertData = [];
+            for (const tmp of datas) {
+                tmp[this.setting.id] = tmp.template_id;
+                tmp[this.setting.pid] = tmp.pid;
+                tmp[this.setting.mid] = stageId;
+                delete tmp.template_id;
+                delete tmp.pid;
+                tmp.id = this.uuid.v4();
+                insertData.push(tmp);
+            }
+            const operate = await transaction.insert(this.tableName, insertData);
+            return operate.affectedRows === datas.length;
+        }
+        /**
+         * 新增数据
+         *
+         * @param {Object} data - 新增的数据(可批量)
+         * @param {Number} stageId - 安全计量期id
+         * @return {Boolean} - 返回新增的结果
+         */
+        async add(data, stageId) {
+            this.transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                result = await this.innerAdd(data, stageId, this.transaction);
+                if (!result) {
+                    throw '新增数据错误';
+                }
+                await this.transaction.commit();
+            } catch (error) {
+                await this.transaction.rollback();
+                result = false;
+            }
+
+            return result;
+        }
+
+        /**
+         * 根据节点Id获取数据
+         *
+         * @param {Number} stageId - 安全计量期id
+         * @param {Number} nodeId - 项目节/工程量清单节点id
+         * @return {Object} - 返回查询到的节点数据
+         */
+        async getDataByNodeId(stageId, nodeId) {
+            if ((nodeId <= 0) || (stageId <= 0)) {
+                return undefined;
+            }
+            const where = {};
+            where[this.setting.mid] = stageId;
+            where[this.setting.id] = nodeId;
+            const data = await this.db.getDataByCondition(where);
+
+            return data;
+        }
+        /**
+         * 根据节点Id获取数据
+         * @param {Number} stageId - 期Id
+         * @param {Array} nodesIds - 节点Id
+         * @return {Array}
+         */
+        async getDataByNodeIds(stageId, nodesIds) {
+            if (stageId <= 0) {
+                return [];
+            }
+
+            const where = {};
+            where[this.setting.mid] = stageId;
+            where[this.setting.id] = nodesIds;
+            const data = await this.db.getAllDataByCondition({ where });
+
+            return this._.sortBy(data, function(d) {
+                return nodesIds.indexOf(d[this.setting.id]);
+            });
+        }
+        /**
+         * 根据主键id获取数据
+         * @param {Array|Number} id - 主键id
+         * @return {Promise<*>}
+         */
+        async getDataByIds(id) {
+            if (!id) return [];
+            const ids = id instanceof Array ? id : [id];
+            if (ids.length === 0) return [];
+
+            const data = await this.db.getAllDataByCondition({ where: { id: ids } });
+            return data;
+        }
+
+        /**
+         * 根据 父节点id 获取子节点
+         * @param stageId
+         * @param nodeId
+         * @return {Promise<*>}
+         */
+        async getChildrenByParentId(stageId, nodeId) {
+            if (stageId <= 0 || !nodeId) {
+                return undefined;
+            }
+            const nodeIds = nodeId instanceof Array ? nodeId : [nodeId];
+            if (nodeIds.length === 0) {
+                return [];
+            }
+
+            const where = {};
+            where[this.setting.mid] = stageId;
+            where[this.setting.pid] = nodeIds;
+            const data = await this.getAllDataByCondition({ where, orders: [[this.setting.order, 'ASC']] });
+
+            return data;
+        }
+
+        async addDutyNode(stage, targetId, count) {
+            if (!stage) return null;
+
+            const select = targetId ? await this.getDataByKid(stage.id, targetId) : null;
+            if (targetId && !select) throw '新增节点数据错误';
+
+            this.transaction = await this.db.beginTransaction();
+            try {
+                if (select) await this._updateChildrenOrder(stage.id, select[this.setting.pid], select[this.setting.order] + 1, count);
+                const newDatas = [];
+                const maxId = await this._getMaxLid(stage.id);
+                for (let i = 1; i < count + 1; i++) {
+                    const newData = {};
+                    newData[this.setting.kid] = maxId + i;
+                    newData[this.setting.pid] = select ? select[this.setting.pid] : this.rootId;
+                    newData[this.setting.mid] = stage.id;
+                    newData[this.setting.level] = select ? select[this.setting.level] : 1;
+                    newData[this.setting.order] = select ? select[this.setting.order] + i : i;
+                    newData[this.setting.fullPath] = newData[this.setting.level] > 1
+                        ? select[this.setting.fullPath].replace('-' + select[this.setting.kid], '-' + newData[this.setting.kid])
+                        : newData[this.setting.kid] + '';
+                    newData[this.setting.isLeaf] = true;
+                    this._getDefaultData(newData, stage);
+                    newDatas.push(newData);
+                }
+                const insertResult = await this.transaction.insert(this.tableName, newDatas);
+                this._cacheMaxLid(stage.id, maxId + count);
+
+                if (insertResult.affectedRows !== count) throw '新增节点数据错误';
+                await this.transaction.commit();
+                this.transaction = null;
+            } catch (err) {
+                await this.transaction.rollback();
+                this.transaction = null;
+                throw err;
+            }
+
+            if (select) {
+                const createData = await this.getChildBetween(stage.id, select[this.setting.pid], select[this.setting.order], select[this.setting.order] + count + 1);
+                const updateData = await this.getNextsData(stage.id, select[this.setting.pid], select[this.setting.order] + count);
+                return {create: createData, update: updateData};
+            } else {
+                const createData = await this.getChildBetween(stage.id, -1, 0, count + 1);
+                return {create: createData};
+            }
+        }
+
+        async updateCalc(stage, data) {
+            const helper = this.ctx.helper;
+            // 简单验证数据
+            if (!stage) throw '成本报审不存在';
+            const decimal = stage.decimal;
+            if (!data) throw '提交数据错误';
+            const datas = data instanceof Array ? data : [data];
+            const ids = datas.map(x => { return x.id; });
+            const orgData = await this.getAllDataByCondition({ where: { id: ids }});
+            const updateData = [];
+            for (const row of datas) {
+                const oData = orgData.find(x => { return x.id === row.id });
+                if (!oData || oData.stage_id !== stage.id || oData.tree_id !== row.tree_id) throw '提交数据错误';
+
+                let nData = { id: oData.id, tree_id: oData.tree_id, update_user_id: this.ctx.session.sessionUser.accountId };
+
+                // calc
+                if (row.quantity !== undefined || row.unit_price !== undefined) {
+                    nData.quantity = row.quantity !== undefined ? helper.round(row.quantity || 0, decimal.qty) : oData.quantity || 0;
+                    nData.unit_price = row.unit_price !== undefined ? helper.round(row.unit_price || 0, decimal.up) : oData.unit_price || 0;
+                    nData.total_price = helper.mul(nData.quantity, nData.unit_price, decimal.tp);
+                }
+                for (const field of costFields.textFields) {
+                    if (row[field] !== undefined) nData[field] = row[field] || '';
+                }
+                updateData.push(nData);
+            }
+
+            await this.db.updateRows(this.tableName, updateData);
+            return { update: updateData };
+        }
+
+        async auditCache(transaction, stageId, auditInfo) {
+            const leaf = await this.getAllDataByCondition({ where: { stage_id: stageId, tree_is_leaf: true} });
+            const updateData = [];
+            for (const l of leaf) {
+                const diff = costFields.curFields.find(x => {
+                    return l[x] !== l['read_' + x];
+                });
+                if (diff || !l.calc_his) {
+                    const data = { id: l.id };
+                    // cache read
+                    for (const f of costFields.curFields) {
+                        data['read_' + f] = l[f];
+                    }
+                    // cache his
+                    const his = l.calc_his ? JSON.parse(l.calc_his) : [];
+                    const fi = his.find(x => { return x.audit_times === auditInfo.audit_times && x.active_order === auditInfo.active_order; });
+                    if (fi >= 0) his.splice(fi, 1);
+                    const newHis = { ...auditInfo };
+                    for (const f of costFields.curFields) {
+                        newHis[f] = l[f];
+                    }
+                    his.push(newHis);
+                    data.calc_his = JSON.stringify(his);
+                    updateData.push(data);
+                }
+            }
+            if (updateData.length > 0) await transaction.updateRows(this.tableName, updateData);
+        }
+
+        async setDecimal(decimal) {
+            if (!this.ctx.costStage) throw '读取数据错误';
+            if (this.ctx.costStage.stage_order !== this.ctx.costStage.latestOrder) throw '往期不可修改小数位数';
+            if (this.ctx.costStage.audit_status !== auditConst.status.uncheck && this.ctx.costStage.audit_status !== auditConst.status.checkNo) throw '仅原报可修改小数位数';
+            const orgDecimal = this.ctx.costStage.decimal;
+
+            const calc = decimal.tp < orgDecimal.tp || decimal.qty !== orgDecimal.qty || decimal.up !== orgDecimal.up;
+            this.ctx.costStage.decimal = this.ctx.helper._.assignIn(this.ctx.costStage.typeInfo.decimal, decimal);
+
+            const updateData = [];
+            if (calc) {
+                const calcData = await this.getAllDataByCondition({ where: {stage_id: this.ctx.costStage.id, tree_is_leaf: 1 } });
+                for (const cd of calcData) {
+                    const nd = { id: cd.id, tree_id: cd.tree_id };
+                    nd.quantity = this.ctx.helper.round(cd.quantity, decimal.qty);
+                    nd.unit_price = this.ctx.helper.round(cd.unit_price, decimal.up);
+                    nd.total_price = this.ctx.helper.mul(cd.quantity, cd.unit_price, decimal.tp);
+                    updateData.push(nd);
+                }
+            }
+
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.update(this.ctx.service.costStage.tableName, { id: this.ctx.costStage.id, decimal: JSON.stringify(this.ctx.costStage.decimal)});
+                if (updateData.length > 0) await conn.updateRows(this.tableName, updateData);
+                await conn.commit();
+                return { calc, bills: updateData };
+            } catch(err) {
+                await conn.rollback();
+                return { calc: false, bills: [] };
+            }
+        }
+
+        async getSum(stage) {
+            const sumFields = costFields.curFields.map(f => { return `SUM(${f}) AS ${f}`});
+            const result = await this.db.queryOne(`SELECT ${sumFields.join(', ')} FROM ${this.tableName} where stage_id = ?`, [stage.id]);
+            return result;
+        }
+    }
+
+    return CostStageLedger;
+};

+ 11 - 0
app/service/report.js

@@ -559,6 +559,14 @@ module.exports = app => {
                             runnableRst.push(service.posCalcDetail.getAllDataByCondition({ where: { tid: params.tender_id } }));
                             runnableKey.push(filter);
                             break;
+                        case 'mem_anc_gcl':
+                            runnableRst.push(service.ancillaryGcl.getAllDataByCondition({ where: { tid: params.tender_id } }));
+                            runnableKey.push(filter);
+                            break;
+                        case 'mem_anc_gcl_detail':
+                            runnableRst.push(service.ancillaryGclDetail.getAllDataByCondition({ where: { tid: params.tender_id } }));
+                            runnableKey.push(filter);
+                            break;
                         default:
                             break;
                     }
@@ -724,6 +732,9 @@ module.exports = app => {
                     case 'mem_spss_compare_stage_pos':
                         rst[filter] = await spssSource.getCompareStagePos();
                         break;
+                    case 'mem_huajing_ledger_check':
+                        const huajingHelper = new rptCustomData.huajingHelper(this.ctx);
+                        rst[filter] = await huajingHelper.getLedgerCheckData(params.tender_id);
                     default:
                         break;
                 }

+ 1 - 0
app/service/tender_permission.js

@@ -48,6 +48,7 @@ module.exports = app => {
                 },
                 cost: {
                     view: { title: '查看', value: 1, isDefault: 1 },
+                    duty_add: { title: '责任成本上报', value: 6 },
                     ledger_add: { title: '成本台账上报', value: 2 },
                     book_add: { title: '财务账面上报', value: 3 },
                     analysis_add: { title: '成本分析上报', value: 4 },

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

@@ -1,4 +1,6 @@
 <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('stage') >= 0 ? 1 : -1) %>"></nav-menu>
+<% if (ctx.app.config.is_debug) { %>
 <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 %>

+ 5 - 4
app/view/cost/audit_btn.ejs

@@ -27,15 +27,16 @@
         <% } %>
     <% } %>
 
-    <% if (ctx.costStage.finalAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0 && ctx.costStage.audit_status === auditConst.status.checked && ctx.costStage.isLatest) { %>
-        <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm btn-block">重新审批</a>
-    <% } %>
-
     <% if (ctx.costStage.cancancel) { %>
         <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-cancel" class="btn btn-danger btn-sm btn-block">撤回</a>
     <% } %>
 
+    <% if (ctx.costStage.stage_type !== 'duty') { %>
+    <% if (ctx.costStage.finalAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0 && ctx.costStage.audit_status === auditConst.status.checked && ctx.costStage.isLatest) { %>
+    <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm btn-block">重新审批</a>
+    <% } %>
     <% if (ctx.costStage.create_user_id === ctx.session.sessionUser.accountId && ctx.costStage.isLatest && (ctx.costStage.audit_status === auditConst.status.checkNo || ctx.costStage.audit_status === auditConst.status.uncheck)) { %>
         <a href="#del-qi" data-toggle="modal" data-target="#del-qi" class="btn btn-outline-danger btn-sm btn-block mt-5">删除本期</a>
     <% } %>
+    <% } %>
 </div>

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

@@ -1,4 +1,6 @@
 <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('stage') >= 0 ? 1 : -1) %>"></nav-menu>
+<% if (ctx.app.config.is_debug) { %>
 <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 %>

+ 82 - 0
app/view/cost/duty.ejs

@@ -0,0 +1,82 @@
+<% 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>
+                <% if (ctx.costStage.create_user_id === ctx.session.sessionUser.accountId) { %>
+                <div class="d-inline-block">
+                    <a href="javascript: void(0);" name="base-opr" type="add" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="delete" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="up-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="down-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                </div>
+                <% } %>
+            </div>
+        </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-0" style="overflow: hidden" id="bills-spread">
+                </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 id="extra" class="tab-pane tab-select-show">
+                        <div class="sjs-height-0" style="height: 518px;">
+                            <table class="table table-sm table-bordered">
+                                <thead>
+                                <tr class="text-center"><th>分类</th><th>金额</th></tr>
+                                </thead>
+                                <tbody id="duty-extra"></tbody>
+                            </table>
+                        </div>
+                    </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>
+                <li class="nav-item">
+                    <a class="nav-link" content="#extra" href="javascript: void(0);">附加信息</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
+<script>
+    const readOnly = <%- ctx.costStage.readOnly %>;
+    const tenderId = parseInt('<%- ctx.tender.id %>');
+    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>

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

@@ -0,0 +1,2 @@
+<% include ./list_menu_list.ejs %>
+<% include ./audit_btn.ejs %>

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

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

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

@@ -1,4 +1,6 @@
 <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('stage') >= 0 ? 1 : -1) %>"></nav-menu>
+<% if (ctx.app.config.is_debug) { %>
 <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 %>

+ 12 - 0
app/view/cost/list_menu_list.ejs

@@ -1,4 +1,7 @@
 <nav-menu title="返回" url="/sp/<%= ctx.subProject.id %>/cost" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
+<% if (ctx.tender.costDuty) { %>
+<nav-menu title="责任成本" url="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/duty/1/stage" ml="3" active="<%= (ctx.url.indexOf('duty') >= 0 ? 1 : -1) %>"></nav-menu>
+<% } %>
 <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>
@@ -7,3 +10,12 @@
 <% 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>
 <% } %>
+<% if (!ctx.tender.costDuty && ctx.permission.cost.duty_add) { %>
+<div class="contarl-box">
+    <form action="/sp/<%= ctx.subProject.id %>/cost/tender/<%- ctx.tender.id %>/addStage" method="post">
+        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+        <input type="hidden" name="stage_type" value="duty" />
+        <button class="btn btn-primary btn-sm btn-block" type="submit"><i class="fa fa-plus"></i> 责任成本</button>
+    </form>
+</div>
+<% } %>

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

@@ -1,12 +1,13 @@
 <div class="panel-sidebar" id="sub-menu">
     <div class="sidebar-title" data-toggle="tooltip" data-placement="right" data-original-title="<%- ctx.tender.data.name %>">
-        <%- `第${ctx.costStage.stage_order}期` %> -
+        <% if (ctx.costStage.stage_type !== 'duty') { %><%- `第${ctx.costStage.stage_order}期` %> - <% } %>
         <%- (ctx.tender.data.name.length > 11 ? ctx.tender.data.name.substring(0,11) + '...' : ctx.tender.data.name) %>
     </div>
     <div class="scrollbar-auto">
         <% if (ctx.costStage.stage_type === 'ledger') { %><% include ./ledger_menu_list.ejs %><% } %>
         <% if (ctx.costStage.stage_type === 'book') { %><% include ./book_menu_list.ejs %><% } %>
         <% if (ctx.costStage.stage_type === 'analysis') { %><% include ./analysis_menu_list.ejs %><% } %>
+        <% if (ctx.costStage.stage_type === 'duty') { %><% include ./duty_menu_list.ejs %><% } %>
         <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-upload fa-rotate-270"></i></a></div>
     </div>
     <script>

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

@@ -8,6 +8,7 @@
         <% if (ctx.costStage.stage_type === 'ledger') { %><% include ./ledger_menu_list.ejs %><% } %>
         <% if (ctx.costStage.stage_type === 'book') { %><% include ./book_menu_list.ejs %><% } %>
         <% if (ctx.costStage.stage_type === 'analysis') { %><% include ./analysis_menu_list.ejs %><% } %>
+        <% if (ctx.costStage.stage_type === 'duty') { %><% include ./duty_menu_list.ejs %><% } %>
         <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-upload fa-rotate-90"></i></a></div>
     </div>
 </div>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 23 - 1
app/view/dashboard/index.ejs


+ 18 - 0
app/view/dashboard/workspace.ejs

@@ -154,6 +154,9 @@
                                             <% if (dashboardStatus.shenpi.costStageAnalysis !== 0) { %>
                                             <option value="costStageAnalysis">成本分析(<%- dashboardStatus.shenpi.costStageAnalysis %>)</option>
                                             <% } %>
+                                            <% if (dashboardStatus.shenpi.costStageDuty !== 0) { %>
+                                            <option value="costStageDuty">责任成本(<%- dashboardStatus.shenpi.costStageDuty %>)</option>
+                                            <% } %>
                                         </select>
                                     </div>
                                 </div>
@@ -514,6 +517,17 @@
                                                             <td><%- (am.begin_time ? ctx.moment(am.begin_time).format('YYYY/MM/DD HH:mm') : '') %></td>
                                                             <td><a href="/sp/<%- am.spid %>/cost/tender/<%- am.tid %>/analysis/<%- am.stage_order %>/stage" class="btn btn-sm btn-table <%- (am.audit_order === 0 ? 'btn-outline-warning text-warning' : 'btn-outline-primary') %>" role="button"><%- (am.audit_order === 0 ? '重新上报' : '审批')%></a></td>
                                                         </tr>
+                                                    <% } else if (db.shenpi_type === 'costStageDuty') { %>
+                                                        <% const am = db; %>
+                                                        <tr data-type="costStageDuty">
+                                                            <% if (am.start_audit === 2) { %><td class="text-center text-danger"><i class="fa fa-exclamation-triangle"></i></td><% } else if (am.start_audit === 1) { %><td class="text-center text-warning"><i class="fa fa-bell"></i></td><% } else { %><td></td><% } %>
+                                                            <td><span class="bg-new-coststage text-new-coststage badge text-width">责任成本</span></td>
+                                                            <td><a href="/sp/<%- am.spid %>/dashboard"><%- am.sp_name %></a></td>
+                                                            <td><a href="/sp/<%- am.spid %>/cost/tender/<%- am.tid %>/duty/1/stage"><%- am.name %></a></td>
+                                                            <td></td>
+                                                            <td><%- (am.begin_time ? ctx.moment(am.begin_time).format('YYYY/MM/DD HH:mm') : '') %></td>
+                                                            <td><a href="/sp/<%- am.spid %>/cost/tender/<%- am.tid %>/duty/1/stage" class="btn btn-sm btn-table <%- (am.audit_order === 0 ? 'btn-outline-warning text-warning' : 'btn-outline-primary') %>" role="button"><%- (am.audit_order === 0 ? '重新上报' : '审批')%></a></td>
+                                                        </tr>
                                                     <% } %>
                                                 <% } %>
                                                 </tbody>
@@ -969,6 +983,10 @@
                                         html.push(`<li class="list-group-item">
                                             <span class="${acCostStage.auditStringClass[n.audit_status]}">${acCostStage.auditString[n.audit_status]}</span> <a href="/sp/${n.spid}/cost/tender/${n.id}/analysis/${n.stage_order}/stage" target="_blank">${n.name}</a> 成本分析 ${n.name}<span class="float-right">${moment(new Date(n.shenpi_time)).format('HH:mm:ss')}</span></li>`);
                                         break;
+                                    case 'costStageDuty':
+                                        html.push(`<li class="list-group-item">
+                                            <span class="${acCostStage.auditStringClass[n.audit_status]}">${acCostStage.auditString[n.audit_status]}</span> <a href="/sp/${n.spid}/cost/tender/${n.id}/duty/1/stage" target="_blank">${n.name}</a> 责任成本 ${n.name}<span class="float-right">${moment(new Date(n.shenpi_time)).format('HH:mm:ss')}</span></li>`);
+                                        break;
                                     default: continue;
                                 }
                             }

+ 2 - 4
app/view/datacollect/index.ejs

@@ -43,10 +43,8 @@
                     <button type="button" class="btn btn-sm btn-outline-dark text-white dropdown-toggle" data-toggle="dropdown" id="zhankai">决策大屏<span><%- ctx.subProject.data_collect %></span></button>
                     <div class="dropdown-menu" aria-labelledby="zhankai">
                         <% for (const i of ctx.subProject.data_collect_pages) { %>
-                        <% if (['6', '7'].includes(i)) { %>
-                            <a class="dropdown-item" href="/sp/<%- ctx.subProject.id %>/datacollect/index/<%- i %>">决策大屏<%- i %></a>
-                        <% } else if (['8'].includes(i)) { %>
-                            <a class="dropdown-item" target="_blank" href="<%- shj_url %>">决策大屏<%- i %></a>
+                        <% if (['6', '7', '8'].includes(i)) { %>
+                            <a class="dropdown-item" href="/sp/<%- ctx.subProject.id %>/datacollect/index/<%- i %>"><% if (i === '7') { %>成本管理<% } else if (i === '8')  { %>投资总控<% } else { %>决策大屏<%- i %><% } %></a>
                         <% } else { %>
                             <a class="dropdown-item change-collect" href="javascript:void(0)" data-collect="<%- i %>">决策大屏<%- i %></a>
                         <% } %>

+ 1 - 5
app/view/datacollect/index4GY18Y.ejs

@@ -61,11 +61,7 @@
                     <button type="button" class="btn btn-sm btn-outline-dark text-white dropdown-toggle" data-toggle="dropdown" id="zhankai">决策大屏<span>6</span></button>
                     <div class="dropdown-menu" aria-labelledby="zhankai">
                         <% for (const i of ctx.subProject.data_collect_pages) { %>
-                            <% if (['8'].includes(i)) { %>
-                                <a class="dropdown-item" target="_blank" href="<%- shj_url %>">决策大屏<%- i %></a>
-                            <% } else { %>
-                                <a class="dropdown-item" href="/sp/<%- ctx.subProject.id %>/datacollect/index/<%- i %>">决策大屏<%- i %></a>
-                            <% } %>
+                            <a class="dropdown-item" href="/sp/<%- ctx.subProject.id %>/datacollect/index/<%- i %>"><% if (i === '7') { %>成本管理<% } else if (i === '8')  { %>投资总控<% } else { %>决策大屏<%- i %><% } %></a>
                         <% } %>
                     </div>
                 </div>

+ 2 - 6
app/view/datacollect/index_cost.ejs

@@ -40,14 +40,10 @@
             <div>
                 <a href="javascript:void(0)"  title="全屏显示" id="showFull" class="text-white"><i class="fa fa-arrows-alt"></i></a>
                 <div class="btn-group">
-                    <button type="button" class="btn btn-sm btn-outline-dark text-white dropdown-toggle" data-toggle="dropdown" id="zhankai">决策大屏<span>7</span></button>
+                    <button type="button" class="btn btn-sm btn-outline-dark text-white dropdown-toggle" data-toggle="dropdown" id="zhankai">成本管理</button>
                     <div class="dropdown-menu" aria-labelledby="zhankai">
                         <% for (const i of ctx.subProject.data_collect_pages) { %>
-                            <% if (['8'].includes(i)) { %>
-                                <a class="dropdown-item" target="_blank" href="<%- shj_url %>">决策大屏<%- i %></a>
-                            <% } else { %>
-                                <a class="dropdown-item" href="/sp/<%- ctx.subProject.id %>/datacollect/index/<%- i %>">决策大屏<%- i %></a>
-                            <% } %>
+                            <a class="dropdown-item" href="/sp/<%- ctx.subProject.id %>/datacollect/index/<%- i %>"><% if (i === '7') { %>成本管理<% } else if (i === '8')  { %>投资总控<% } else { %>决策大屏<%- i %><% } %></a>
                         <% } %>
                     </div>
                 </div>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1017 - 0
app/view/datacollect/index_tz.ejs


+ 0 - 7
app/view/layout/menu.ejs

@@ -37,17 +37,10 @@
             <% } %>
             <% } else { %>
             <li <% if((ctx.controllerName === menu.controller && (!menu.notIncludedUrl || !menu.notIncludedUrl.some(item => ctx.url.includes(item)))) || (!!menu.controllers && menu.controllers.indexOf(ctx.controllerName) >= 0 && (!menu.includedUrl || !menu.includedUrl[ctx.controllerName] || menu.includedUrl[ctx.controllerName].some(item => ctx.url.includes(item)))) || (menu.url && menu.url === ctx.url)) { %>class="active"<% } %>>
-                <% if (menu.controller === 'datacollect' && ctx.subProject.data_collect_pages.includes('8') && ctx.subProject.data_collect === 8) { %>
-                <a href="<%- menu.url %>" target="_blank" id="<%- 'nav_' + index%>" data-toggle="tooltip" data-placement="right" title="" data-original-title="<%- menu.name %>">
-                    <i class="fa <%- menu.icon %>"></i>
-                    <% if (menu.caption) { %><span class="d-inline-block" style="vertical-align: text-bottom;"><%- menu.caption %></span><% } %>
-                </a>
-                <% } else { %>
                 <a href="<%- menu.url %>" id="<%- 'nav_' + index%>" data-toggle="tooltip" data-placement="right" title="" data-original-title="<%- menu.name %>">
                     <i class="fa <%- menu.icon %>"></i>
                     <% if (menu.caption) { %><span class="d-inline-block" style="vertical-align: text-bottom;"><%- menu.caption %></span><% } %>
                 </a>
-                <% } %>
             </li>
             <% } %>
             <% } %>

+ 1 - 1
app/view/safe/inspection_information.ejs

@@ -5,7 +5,7 @@
             <% include ./sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
-                    质量巡检
+                    安全巡检
                 </div>
             </div>
             <div class="ml-auto">

+ 62 - 18
app/view/sp_setting/datacollect.ejs

@@ -1,3 +1,42 @@
+<link href="/public/css/bootstrap/select2.min.css" rel="stylesheet" />
+<link rel="stylesheet" href="/public/css/bootstrap/select2-bootstrap4.min.css">
+<script src="/public/js/bootstrap/select2.min.js"></script>
+<style>
+    .select2-container {
+        /*display: inline-block!important;*/
+        /*height: 27px;*/
+        width: 100% !important;
+    }
+    .select2-container--bootstrap4 .select2-selection--single {
+        height: calc(1.1em + .75rem + 2px) !important;
+    }
+    /*.select2-container--bootstrap4 .select2-selection {*/
+    /*    background-color: #f8f9fa;*/
+    /*    !*border-color: #f8f9fa;*!*/
+    /*    border: 1px solid #f8f9fa;*/
+    /*}*/
+    /*.select2-container--bootstrap4 .select2-selection--single .select2-selection__rendered:focus{*/
+    /*    box-shadow: 0 0 0 0.2rem rgba(216,217,219,.5);*/
+    /*}*/
+    /*.select2-container--bootstrap4 .select2-selection--single .select2-selection__rendered:hover {*/
+    /*    background-color: #e2e6ea;*/
+    /*    border-color: #dae0e5;*/
+    /*}*/
+    .select2-container--bootstrap4 .select2-selection--single .select2-selection__rendered {
+        line-height: calc(1.1em + .75rem);
+        /*color: #007bff!important;*/
+        border-radius: 0.2rem;
+        /*background-color: #f8f9fa;*/
+        /*border-color: #f8f9fa;*/
+    }
+    .select2-container--bootstrap4 .select2-selection--single .select2-selection__arrow b {
+        border-color: #007bff transparent transparent transparent;
+        border-width: 4px 3.7px 0;
+    }
+    .select2-search--dropdown .select2-search__field {
+        padding: 0.175rem 0.5rem;
+    }
+</style>
 <% include ./sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
@@ -24,10 +63,12 @@
                         <a class="nav-item nav-link<% if (ctx.subProject.data_collect === is_dz2) { %> active<% } %>" data-datacollect="<%- is_dz2 %>" data-toggle="tab" href="#shujudaping-<%- is_dz2 %>" role="tab">
                             决策大屏<%- ctx.helper.transFormToChinese(is_dz2) %><% if (ctx.helper._.indexOf(ctx.subProject.data_collect_pages, is_dz2.toString()) !== -1) { %>(已开启<% if (ctx.subProject.data_collect === is_dz2) { %>、默认<% } %>)<% } %></a>
                     <% } %>
-                    <a class="nav-item nav-link<% if (ctx.subProject.data_collect === 7) { %> active<% } %>" data-datacollect="7" data-toggle="tab" href="#shujudaping-7" role="tab">
-                        决策大屏<%- ctx.helper.transFormToChinese(7) %><% if (ctx.helper._.indexOf(ctx.subProject.data_collect_pages, '7') !== -1) { %>(已开启<% if (ctx.subProject.data_collect === 7) { %>、默认<% } %>)<% } %></a>
-                    <a class="nav-item nav-link<% if (ctx.subProject.data_collect === is_dz3) { %> active<% } %>" data-datacollect="<%- is_dz3 %>" data-toggle="tab" href="#shujudaping-<%- is_dz3 %>" role="tab">
-                        决策大屏<%- ctx.helper.transFormToChinese(is_dz3) %><% if (ctx.helper._.indexOf(ctx.subProject.data_collect_pages, is_dz3.toString()) !== -1) { %>(已开启<% if (ctx.subProject.data_collect === is_dz3) { %>、默认<% } %>)<% } %></a>
+                    <% if (is_dzcb) { %>
+                        <a class="nav-item nav-link<% if (ctx.subProject.data_collect === is_dzcb) { %> active<% } %>" data-datacollect="<%- is_dzcb %>" data-toggle="tab" href="#shujudaping-<%- is_dzcb %>" role="tab">
+                            成本管理<% if (ctx.helper._.indexOf(ctx.subProject.data_collect_pages, is_dzcb.toString()) !== -1) { %>(已开启<% if (ctx.subProject.data_collect === is_dzcb) { %>、默认<% } %>)<% } %></a>
+                    <% } %>
+                    <a class="nav-item nav-link<% if (ctx.subProject.data_collect === is_dztz) { %> active<% } %>" data-datacollect="<%- is_dztz %>" data-toggle="tab" href="#shujudaping-<%- is_dztz %>" role="tab">
+                        投资总控<% if (ctx.helper._.indexOf(ctx.subProject.data_collect_pages, is_dztz.toString()) !== -1) { %>(已开启<% if (ctx.subProject.data_collect === is_dztz) { %>、默认<% } %>)<% } %></a>
                     <div class="ml-auto">
                         <!--<div class="form-check form-check-inline">-->
                             <!--<input class="form-check-input" type="checkbox" id="show-datacollect" value="option1" <% if (ctx.subProject.data_collect) { %>checked<% } %>>-->
@@ -61,12 +102,12 @@
                             <span>四川广元路桥集团计量内控管理云平台定制(GY18Y)</span><a class="nav-link" style="display: inline-block;" data-toggle="modal" href="#dpsix-set" data-target="#dpsix-set" title="决策大屏六设置"><i class="fa fa-cog"></i></a><img src="/public/images/juecedaping0<%- is_dz2 %>.png" width="100%">
                         </div>
                     <% } %>
-                    <div id="shujudaping-7" class="tab-pane<% if (ctx.subProject.data_collect === 7) { %> active<% } %>">
-                        成本管理决策大屏数据汇总<img src="/public/images/juecedaping07.png" width="100%">
+                    <div id="shujudaping-<%- is_dzcb %>" class="tab-pane<% if (ctx.subProject.data_collect === is_dzcb) { %> active<% } %>">
+                        <span>成本管理决策大屏定制(Y3T2Q)</span><img src="/public/images/juecedaping0<%- is_dzcb %>.png" width="100%">
                     </div>
-                    <% if (is_dz3) { %>
-                    <div id="shujudaping-<%- is_dz3 %>" class="tab-pane<% if (ctx.subProject.data_collect === is_dz3) { %> active<% } %>">
-                        山海鲸数据大屏平台定制(会打开新标签页跳转)<img src="/public/images/juecedaping0<%- is_dz3 %>.png" width="100%">
+                    <% if (is_dztz) { %>
+                    <div id="shujudaping-<%- is_dztz %>" class="tab-pane<% if (ctx.subProject.data_collect === is_dztz) { %> active<% } %>">
+                        <span>投资总控决策大屏定制(Y3T2Q)</span><a class="nav-link" style="display: inline-block;" data-toggle="modal" href="#dpeight-set" data-target="#dpeight-set" title="决策大屏八设置"><i class="fa fa-cog"></i></a><img src="/public/images/juecedaping0<%- is_dztz %>.png" width="100%">
                     </div>
                     <% } %>
                 </div>
@@ -91,7 +132,8 @@
     let addDataCollect = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.subProject.page_show.addDataCollect)) %>'));
     let is_dz1 = <% if (is_dz1) { %>parseInt('<%- is_dz1 %>');<% } else { %>false;<% } %>
     let is_dz2 = <% if (is_dz2) { %>parseInt('<%- is_dz2 %>');<% } else { %>false;<% } %>
-    let is_dz3 = <% if (is_dz3) { %>parseInt('<%- is_dz3 %>');<% } else { %>false;<% } %>
+    let is_dzcb = <% if (is_dzcb) { %>parseInt('<%- is_dzcb %>');<% } else { %>false;<% } %>
+    let is_dztz = <% if (is_dztz) { %>parseInt('<%- is_dztz %>');<% } else { %>false;<% } %>
     $(function () {
         autoFlashHeight();
         // 开启及关闭展示
@@ -168,17 +210,19 @@
                 }
                 $('#tablist a').eq(is_dz2-3).text('决策大屏' + transFormToChinese(is_dz2) + msg);
             }
-            let msg = '';
-            if (_.indexOf(dataCollectPages, '7') !== -1) {
-                msg = '(已开启' + (7 === dataCollect ? '、默认' : '') + ')' ;
+            if (is_dzcb) {
+                let msg = '';
+                if (_.indexOf(dataCollectPages, is_dzcb.toString()) !== -1) {
+                    msg = '(已开启' + (is_dzcb === dataCollect ? '、默认' : '') + ')' ;
+                }
+                $('#tablist a').eq(is_dzcb-3).text('成本管理' + msg);
             }
-            $('#tablist a').eq(7-3).text('决策大屏' + transFormToChinese(7) + msg);
-            if (is_dz3) {
+            if (is_dztz) {
                 let msg = '';
-                if (_.indexOf(dataCollectPages, is_dz3.toString()) !== -1) {
-                    msg = '(已开启' + (is_dz3=== dataCollect ? '、默认' : '') + ')' ;
+                if (_.indexOf(dataCollectPages, is_dztz.toString()) !== -1) {
+                    msg = '(已开启' + (is_dztz=== dataCollect ? '、默认' : '') + ')' ;
                 }
-                $('#tablist a').eq(is_dz3-3).text('决策大屏' + transFormToChinese(is_dz3) + msg);
+                $('#tablist a').eq(is_dztz-3).text('投资总控' + msg);
             }
         }
         $('#tablist a').click(function () {

+ 204 - 3
app/view/sp_setting/datacollect_modal.ejs

@@ -285,6 +285,207 @@
     })
 </script>
 <% } %>
+<% if (is_dzcb) { %>
+    <!--决策大屏七设置-->
+    <div class="modal fade" id="dpeight-set" 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">
+                    <ul class="list-unstyled">
+                        <li class="mb-4">
+                            <h6 class="mb-3">标段一设置</h6>
+                            <!-- 总包 -->
+                            <div class="form-group row">
+                                <label class="col-sm-2 col-form-label">总包</label>
+                                <div class="col-sm-10">
+                                    <select class="form-control form-control-sm" id="tender1a_select">
+                                        <option value="0">请选择标段</option>
+                                        <% for (const t of tenders) { %>
+                                            <option value="<%= t.id %>"
+                                                    <% if (ctx.helper._.find(daPing08Set.tenders, { id: 1 }).a === t.id) { %>selected<% } %>>
+                                                <%= t.name %>(ID=<%= t.id %>)
+                                            </option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                            </div>
+
+                            <!-- 分包 -->
+                            <div class="form-group row mb-0">
+                                <label class="col-sm-2 col-form-label">分包</label>
+                                <div class="col-sm-10">
+                                    <select class="form-control form-control-sm" id="tender1b_select">
+                                        <option value="0">请选择标段</option>
+                                        <% for (const t of tenders) { %>
+                                            <option value="<%= t.id %>"
+                                                    <% if (ctx.helper._.find(daPing08Set.tenders, { id: 1 }).b === t.id) { %>selected<% } %>>
+                                                <%= t.name %>(ID=<%= t.id %>)
+                                            </option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                            </div>
+                        </li>
+                        <li class="mb-4">
+                            <h6 class="mb-3">标段二设置</h6>
+
+                            <!-- 总包 -->
+                            <div class="form-group row">
+                                <label class="col-sm-2 col-form-label">总包</label>
+                                <div class="col-sm-10">
+                                    <select class="form-control form-control-sm" id="tender2a_select">
+                                        <option value="0">请选择标段</option>
+                                        <% for (const t of tenders) { %>
+                                            <option value="<%= t.id %>"
+                                                    <% if (ctx.helper._.find(daPing08Set.tenders, { id: 2 }).a === t.id) { %>selected<% } %>>
+                                                <%= t.name %>(ID=<%= t.id %>)
+                                            </option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                            </div>
+
+                            <!-- 分包 -->
+                            <div class="form-group row mb-0">
+                                <label class="col-sm-2 col-form-label">分包</label>
+                                <div class="col-sm-10">
+                                    <select class="form-control form-control-sm" id="tender2b_select">
+                                        <option value="0">请选择标段</option>
+                                        <% for (const t of tenders) { %>
+                                            <option value="<%= t.id %>"
+                                                    <% if (ctx.helper._.find(daPing08Set.tenders, { id: 2 }).b === t.id) { %>selected<% } %>>
+                                                <%= t.name %>(ID=<%= t.id %>)
+                                            </option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                            </div>
+                        </li>
+                        <li class="mb-4">
+                            <h6 class="mb-3">标段三设置</h6>
+
+                            <!-- 总包 -->
+                            <div class="form-group row">
+                                <label class="col-sm-2 col-form-label">总包</label>
+                                <div class="col-sm-10">
+                                    <select class="form-control form-control-sm" id="tender3a_select">
+                                        <option value="0">请选择标段</option>
+                                        <% for (const t of tenders) { %>
+                                            <option value="<%= t.id %>"
+                                                    <% if (ctx.helper._.find(daPing08Set.tenders, { id: 3 }).a === t.id) { %>selected<% } %>>
+                                                <%= t.name %>(ID=<%= t.id %>)
+                                            </option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                            </div>
+
+                            <!-- 分包 -->
+                            <div class="form-group row mb-0">
+                                <label class="col-sm-2 col-form-label">分包</label>
+                                <div class="col-sm-10">
+                                    <select class="form-control form-control-sm" id="tender3b_select">
+                                        <option value="0">请选择标段</option>
+                                        <% for (const t of tenders) { %>
+                                            <option value="<%= t.id %>"
+                                                    <% if (ctx.helper._.find(daPing08Set.tenders, { id: 3 }).b === t.id) { %>selected<% } %>>
+                                                <%= t.name %>(ID=<%= t.id %>)
+                                            </option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                            </div>
+                        </li>
+                        <li class="mb-4">
+                            <h6 class="mb-3">标段四设置</h6>
+
+                            <!-- 总包 -->
+                            <div class="form-group row">
+                                <label class="col-sm-2 col-form-label">总包</label>
+                                <div class="col-sm-10">
+                                    <select class="form-control form-control-sm" id="tender4a_select">
+                                        <option value="0">请选择标段</option>
+                                        <% for (const t of tenders) { %>
+                                            <option value="<%= t.id %>"
+                                                    <% if (ctx.helper._.find(daPing08Set.tenders, { id: 4 }).a === t.id) { %>selected<% } %>>
+                                                <%= t.name %>(ID=<%= t.id %>)
+                                            </option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                            </div>
+
+                            <!-- 分包 -->
+                            <div class="form-group row mb-0">
+                                <label class="col-sm-2 col-form-label">分包</label>
+                                <div class="col-sm-10">
+                                    <select class="form-control form-control-sm" id="tender4b_select">
+                                        <option value="0">请选择标段</option>
+                                        <% for (const t of tenders) { %>
+                                            <option value="<%= t.id %>"
+                                                    <% if (ctx.helper._.find(daPing08Set.tenders, { id: 4 }).b === t.id) { %>selected<% } %>>
+                                                <%= t.name %>(ID=<%= t.id %>)
+                                            </option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                            </div>
+                        </li>
+                    </ul>
+                    <div class="mt-2">提示:利润等于 总包-分包</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="dp08_save_btn">确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <script>
+        const daPing08Set = JSON.parse(unescape('<%- escape(JSON.stringify(daPing08Set)) %>'));
+        console.log(daPing08Set);
+        $(function () {
+            for (const t of daPing08Set.tenders) {
+                $(`#tender${t.id}a_select`).select2({
+                    language: 'zh-CN',
+                    theme: 'bootstrap4',
+                    selectOnClose: true,
+                    // width: '150',
+                });
+                $(`#tender${t.id}b_select`).select2({
+                    language: 'zh-CN',
+                    theme: 'bootstrap4',
+                    selectOnClose: true,
+                    // width: '150',
+                });
+            }
+            $('#dpeight-set').on('show.bs.modal', function (e) {
+                console.log(daPing08Set);
+                for (const t of daPing08Set.tenders) {
+                    $(`#tender${t.id}a_select`).val(t.a);
+                    $(`#tender${t.id}b_select`).val(t.b);
+                }
+            });
+            $('#dp08_save_btn').click(function () {
+                let tenders = [];
+                for (let i = 1; i <= 4; i++) {
+                    const a = parseInt($(`#tender${i}a_select`).val());
+                    const b = parseInt($(`#tender${i}b_select`).val());
+                    tenders.push({ id: i, a: a, b: b });
+                }
+                daPing08Set.tenders = tenders;
+                console.log(daPing08Set);
+                postData('/sp/' + spid + '/setting/datacollect/save', { type: 'save-dp08', daPing08Set }, function (result) {
+                    toastr.success('设置成功');
+                    $('#dpeight-set').modal('hide');
+                })
+            });
+        })
+    </script>
+<% } %>
 <script>
     $(function () {
         let timer = null
@@ -427,8 +628,8 @@
 
             const selectedIds = dataCollectAudits.map(audit => {
                 const id = parseInt(audit.uid, 10);
-                return isNaN(id) ? null : id; 
-            }).filter(id => id !== null); 
+                return isNaN(id) ? null : id;
+            }).filter(id => id !== null);
 
             $('.user-item').each(function() {
                 const userIdStr = $(this).data('id');
@@ -440,7 +641,7 @@
                     $(this).addClass('bg-light');
                 } else {
                     $mark.hide();
-                    $(this).removeClass('bg-light'); 
+                    $(this).removeClass('bg-light');
                 }
             });
         }

+ 2 - 2
app/view/template/pos_calc.ejs

@@ -23,8 +23,8 @@
                     <div id="detail-ctrl" style="display: none;">
                         <a href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="reset"> 重置</a>
                         <a href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="save"> 保存</a>
-                        <!--<a href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="export"> 导出</a>-->
-                        <!--<a href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="import"> 导入</a>-->
+                        <a href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="export"> 导出</a>
+                        <a href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="import"> 导入</a>
                     </div>
                     <div id="detail-user-info" class="ml-auto"></div>
                 </div>

+ 24 - 0
config/web.js

@@ -2576,6 +2576,30 @@ const JsFiles = {
                 ],
                 mergeFile: 'cost_stage',
             },
+            cost_stage_duty: {
+                files: [
+                    '/public/js/js-xlsx/xlsx.full.min.js',
+                    '/public/js/js-xlsx/xlsx.utils.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
+                    '/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/path_tree.js',
+                    '/public/js/shares/tools_att.js',
+                    '/public/js/shares/common_audit.js',
+                    '/public/js/cost_stage_duty.js',
+                ],
+                mergeFile: 'cost_stage_duty',
+            },
             cost_stage_ledger: {
                 files: [
                     '/public/js/js-xlsx/xlsx.full.min.js',

+ 37 - 0
sql/update.sql

@@ -75,6 +75,43 @@ ADD COLUMN `ctrl_profit` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '控制目标
 ADD COLUMN `real_optimize_rate` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '实际投资-优化率' AFTER `ctrl_profit`,
 ADD COLUMN `real_profit` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '实际投资-收益金额' AFTER `real_optimize_rate`;
 
+CREATE TABLE `zh_cost_stage_duty` (
+  `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid',
+  `cost_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(不同期保持统一)',
+  `tender_id` int(11) unsigned NOT NULL COMMENT '标段id',
+  `stage_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
+  `tree_id` int(11) NOT NULL COMMENT '节点id',
+  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
+  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
+  `tree_order` mediumint(4) NOT NULL COMMENT '同级排序',
+  `tree_full_path` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path-tree_id',
+  `tree_is_leaf` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '是否叶子节点,界面显示辅助字段',
+  `code` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号',
+  `b_code` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '清单编号',
+  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
+  `unit` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '单位',
+  `unit_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '单价',
+  `quantity` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '数量',
+  `total_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '金额',
+  `drawing_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '图册号',
+  `memo` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
+  `add_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人',
+  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后编辑人',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
+  `read_unit_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '单价-只读',
+  `read_quantity` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '数量-只读',
+  `read_total_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '金额-只读',
+  `calc_his` json DEFAULT NULL COMMENT '本期历史',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+ALTER TABLE `zh_cost_stage`
+ADD COLUMN `extra_info` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '附加信息' AFTER `calc_template`;
+
+ALTER TABLE `zh_budget_final_list`
+ADD COLUMN `contract` varchar(1000) NOT NULL DEFAULT ''  COMMENT '关联合同id(,分隔)' AFTER `status`;
+
 ------------------------------------
 -- 表数据
 ------------------------------------