Просмотр исходного кода

1. 计算模板,设计量细目,复制模板、粘贴模板
2. 报表数据源:成本报审、财务账面、成本分析

MaiXinRong 1 месяц назад
Родитель
Сommit
3405f9e458

+ 3 - 0
app/const/source_type.js

@@ -14,6 +14,9 @@ const sourceTypeData = [
     { id: 101, name: '安全生产费', key: 'payment_safe', params: { tender_id: 1, detail_id: 1 } },
     { id: 200, name: '项目进度', key: 'budget', params: { sp_id: 'uuid', budget_id: 1 } },
     { id: 300, name: '合同管理', key: 'contract', params: { tender_id: 1 } },
+    { id: 400, name: '成本报审', key: 'cost_ledger', params: { tender_id: 1, cost_stage_id: 'uuid' } },
+    { id: 401, name: '财务账面', key: 'cost_book', params: { tender_id: 1, cost_stage_id: 'uuid' } },
+    { id: 402, name: '成本分析', key: 'cost_analysis', params: { tender_id: 1, cost_stage_id: 'uuid' } },
     // { id: 301, name: '标段合同', key: 'tender_contract' },
 ];
 

+ 1 - 1
app/controller/spss_controller.js

@@ -946,7 +946,7 @@ module.exports = app => {
         }
 
         async _testLoadReport(data) {
-            const reportData = await this.ctx.service.report.getReportData(data.source_type, data.params, data.source_filters);
+            const reportData = await this.ctx.service.report.getReportData(data.source_type, data.params, data.source_filters, {});
             this.ctx.body = { err: 0, msg: '', data: reportData };
         }
         async testLoad(ctx) {

+ 3 - 1
app/controller/template_controller.js

@@ -190,7 +190,7 @@ module.exports = app => {
             try {
                 const template = await ctx.service.calcTmpl.getTemplate(ctx.query.tid);
                 if (!template) throw '计算模板不存在';
-                if (template.project_id !== ctx.session.sessionProject.id) throw '您无权导出该模板数据';
+                if (template.pid !== ctx.session.sessionProject.id) throw '您无权导出该模板数据';
 
                 const Cpd = require('../lib/crypt').cpd;
                 const cpd = new Cpd();
@@ -213,6 +213,7 @@ module.exports = app => {
                 });
                 ctx.body = await fs.readFileSync(filepath);
             } catch(err) {
+                console.log(err);
                 ctx.log(err);
                 ctx.postError(err, '导出模板数据失败');
             }
@@ -242,6 +243,7 @@ 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 (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 } });

+ 161 - 0
app/lib/rm/cost_analysis_stage.js

@@ -0,0 +1,161 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const RptMemBase = require('./base');
+const bindData = {};
+const Ledger = require('../ledger');
+const commonCalcFields = ['yf_excl_tax_tp', 'sf_excl_tax_tp', 'in_excl_tax_tp', 'num_a', 'num_b', 'num_c', 'num_d', 'num_e', 'num_f', 'num_g', 'num_h', 'num_i', 'num_j', 'num_k', 'num_l', 'num_m', 'num_n', 'num_o', 'num_p'];
+const specCalcFeilds = ['sf_percent'];
+const calcFields = [...commonCalcFields, ...specCalcFeilds];
+
+class rptMemChange extends RptMemBase {
+    constructor(ctx) {
+        super(ctx, bindData);
+    }
+
+    async doCheckCostStage(stage_id) {
+        if (this.ctx.costStage) return;
+        this.ctx.costStage = await this.ctx.service.costStage.getStage(stage_id);
+        await this.ctx.service.costStage.doCheckStage(this.ctx.costStage);
+    }
+
+    async doCheckTender(tenderId) {
+        if (this.ctx.tender) return;
+        this.ctx.tender = { id: tenderId };
+        this.ctx.tender.data = await this.ctx.service.tender.getTender(tenderId);
+        this.ctx.tender.info = await this.ctx.service.tenderInfo.getTenderInfo(tenderId);
+    }
+
+    async doBeforeLoadReport(params) {
+        await this.doCheckCostStage(params.cost_stage_id);
+        await this.doCheckTender(this.ctx.costStage.tid);
+    }
+
+    async getCostStageAnalysis() {
+        const bills = this.ctx.costStage.readOnly
+            ? await this.ctx.service.costStageAnalysis.getReadData(this.ctx.costStage)
+            : await this.ctx.service.costStageAnalysis.getEditData(this.ctx.costStage);
+        const setting = {
+            id: 'tree_id',
+            pid: 'tree_pid',
+            order: 'tree_order',
+            level: 'tree_level',
+            isLeaf: 'tree_is_leaf',
+            fullPath: 'tree_full_path',
+            rootId: -1,
+            calcFields: commonCalcFields,
+        };
+        const billsTree = new Ledger.billsTree(this.ctx, setting);
+        billsTree.loadDatas(bills);
+        billsTree.calculateAll();
+        return billsTree.getDefaultDatas();
+    }
+    async getCostStageAnalysisDetail() {
+        const detail = this.ctx.costStage.readOnly
+            ? await this.ctx.service.costStageAnalysisDetail.getReadData(this.ctx.costStage)
+            : await this.ctx.service.costStageAnalysisDetail.getEditData(this.ctx.costStage);
+        return detail;
+    }
+    _analysisCompareData(datas, roles) {
+        const findHis = function(role, history) {
+            let his = null;
+            for (const h of history) {
+                if (h.times < role.times || (h.audit_times === role.audit_times && h.active_order <= role.active_order)) {
+                    his = h;
+                } else {
+                    break;
+                }
+            }
+            return his;
+        };
+        for (const d of datas) {
+            if (!d.tree_is_leaf) continue;
+            for (const prop of calcFields) {
+                d[`his_${prop}`] = [];
+            }
+            d.calc_his.sort((x, y) => { return x.audit_times === y.audit_times ? x.active_order - y.active_order : x.audit_times - y.audit_times; });
+            for (const r of roles) {
+                if (r.latest) {
+                    for (const prop of calcFields) {
+                        d[`r_${prop}_${r.audit_order}`] = d[prop];
+                        d[`his_${prop}`].push(d[prop]);
+                    }
+                } else {
+                    const rHis = findHis(r, d.calc_his);
+                    if (rHis) {
+                        for (const prop of calcFields) {
+                            d[`r_${prop}_${r.audit_order}`] = rHis[prop];
+                        }
+                    }
+                    for (const prop of calcFields) {
+                        d[`his_${prop}`].push(rHis ? rHis[prop] : 0);
+                    }
+                }
+            }
+        }
+    }
+    async getCostStageAnalysisCompare() {
+        const bills = await this.ctx.service.costStageAnalysis.getCompareData(this.ctx.costStage);
+        const roles = await this.ctx.service.costStageAudit.getViewFlow(this.ctx.costStage);
+        this._analysisCompareData(bills, roles);
+        const setting = {
+            id: 'tree_id',
+            pid: 'tree_pid',
+            order: 'tree_order',
+            level: 'tree_level',
+            isLeaf: 'tree_is_leaf',
+            fullPath: 'tree_full_path',
+            rootId: -1,
+            calcFields: [],
+        };
+        for(const r of roles) {
+            for (const f of commonCalcFields) {
+                setting.calcFields.push(`r_${f}_${r.audit_order}`);
+            }
+        }
+        const compareTree = new Ledger.billsTree(this.ctx, setting);
+        compareTree.loadDatas(bills);
+        compareTree.calculateAll();
+        return compareTree.getDefaultDatas();
+    }
+    async getCostStageAnalysisDetailCompare() {
+        const detail = await this.ctx.service.costStageAnalysisDetail.getCompareData(this.ctx.costStage);
+        const roles = await this.ctx.service.costStageAudit.getViewFlow(this.ctx.costStage);
+        this._analysisCompareData(detail, roles);
+        return detail;
+    }
+
+    getCommonData(params, tableName, fields, customDefine, customSelect) {
+        switch (tableName) {
+            case 'mem_project':
+                return this.ctx.service.project.getDataByCondition({ id: this.ctx.session.sessionProject.id });
+            case 'mem_tender':
+                return [this.ctx.tender.data];
+            case 'mem_tender_info':
+                return [this.ctx.tender.info];
+            case 'mem_cost_stage':
+                return [this.ctx.costStage];
+            case 'mem_cost_stage_audit':
+                return this.ctx.service.costStageAudit.getFinalUniqAuditors(this.ctx.costStage);
+            case 'mem_cost_stage_analysis':
+                return this.getCostStageAnalysis();
+            case 'mem_cost_stage_analysis_detail':
+                return this.getCostStageAnalysisDetail();
+            case 'mem_cost_stage_analysis_compare':
+                return this.getCostStageAnalysisCompare();
+            case 'mem_cost_stage_analysis_detail_compare':
+                return this.getCostStageAnalysisDetailCompare();
+            default:
+                return [];
+        }
+    }
+}
+
+module.exports = rptMemChange;

+ 185 - 0
app/lib/rm/cost_book_stage.js

@@ -0,0 +1,185 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const RptMemBase = require('./base');
+const bindData = {};
+const Ledger = require('../ledger');
+const calcFields = ['in_tp', 'in_excl_tax_tp'];
+
+class rptMemChange extends RptMemBase {
+    constructor(ctx) {
+        super(ctx, bindData);
+    }
+
+    async doCheckCostStage(stage_id) {
+        if (this.ctx.costStage) return;
+        this.ctx.costStage = await this.ctx.service.costStage.getStage(stage_id);
+        await this.ctx.service.costStage.doCheckStage(this.ctx.costStage);
+        if (this.ctx.costStage.rela_stage && this.ctx.costStage.rela_stage.sid)
+            this.ctx.costStage.relaStage = await this.ctx.service.costStage.getStage(this.ctx.costStage.rela_stage.sid);
+    }
+
+    async doCheckTender(tenderId) {
+        if (this.ctx.tender) return;
+        this.ctx.tender = { id: tenderId };
+        this.ctx.tender.data = await this.ctx.service.tender.getTender(tenderId);
+        this.ctx.tender.info = await this.ctx.service.tenderInfo.getTenderInfo(tenderId);
+    }
+
+    async doBeforeLoadReport(params) {
+        await this.doCheckCostStage(params.cost_stage_id);
+        await this.doCheckTender(this.ctx.costStage.tid);
+    }
+
+    async getCostStageBook() {
+        const ledger = await this.ctx.service.costStageLedger.getReadData(this.ctx.costStage.relaStage);
+        ledger.forEach(l => { delete l.postil; delete l.memo; });
+        const book = this.ctx.costStage.readOnly
+            ? await this.ctx.service.costStageBook.getReadData(this.ctx.costStage)
+            : await this.ctx.service.costStageBook.getEditData(this.ctx.costStage);
+        this.ctx.helper.assignRelaData(ledger, [
+            { data: book, fields: ['pre_in_tp', 'pre_in_excl_tax_tp', 'in_tp', 'in_excl_tax_tp', 'postil', 'memo'], prefix: '', relaId: 'ledger_id' },
+            { data: book, fields: ['id'], prefix: 'book_', relaId: 'ledger_id' },
+        ]);
+
+        const setting = {
+            id: 'tree_id',
+            pid: 'tree_pid',
+            order: 'tree_order',
+            level: 'tree_level',
+            isLeaf: 'tree_is_leaf',
+            fullPath: 'tree_full_path',
+            rootId: -1,
+            calcFields: ['pre_in_tp', 'pre_in_excl_tax_tp', 'in_tp', 'in_excl_tax_tp'],
+        };
+        const billsTree = new Ledger.billsTree(this.ctx, setting);
+        billsTree.loadDatas(ledger);
+        billsTree.calculateAll();
+        return billsTree.getDefaultDatas();
+    }
+    async getCostStageBookDetail() {
+        const detail = await this.ctx.service.costStageDetail.getReadData(this.ctx.costStage.relaStage);
+        detail.forEach(l => { delete l.postil; delete l.memo; });
+        const bookDetail = this.ctx.costStage.readOnly
+            ? await this.ctx.service.costStageBookDetail.getReadData(this.ctx.costStage)
+            : await this.ctx.service.costStageBookDetail.getEditData(this.ctx.costStage);
+        this.ctx.helper.assignRelaData(detail, [
+            { data: bookDetail, fields: ['in_tp', 'in_excl_tax_tp', 'postil', 'memo'], prefix: '', relaId: 'detail_id' },
+            { data: bookDetail, fields: ['id'], prefix: 'book_', relaId: 'detail_id' },
+        ]);
+        return detail;
+    }
+    _analysisCompareData(datas, roles) {
+        const findHis = function(role, history) {
+            let his = null;
+            for (const h of history) {
+                if (h.times < role.times || (h.audit_times === role.audit_times && h.active_order <= role.active_order)) {
+                    his = h;
+                } else {
+                    break;
+                }
+            }
+            return his;
+        };
+        for (const d of datas) {
+            if (!d.tree_is_leaf) continue;
+            for (const prop of calcFields) {
+                d[`his_${prop}`] = [];
+            }
+            d.calc_his.sort((x, y) => { return x.audit_times === y.audit_times ? x.active_order - y.active_order : x.audit_times - y.audit_times; });
+            for (const r of roles) {
+                if (r.latest) {
+                    for (const prop of calcFields) {
+                        d[`r_${prop}_${r.audit_order}`] = d[prop];
+                        d[`his_${prop}`].push(d[prop]);
+                    }
+                } else {
+                    const rHis = findHis(r, d.calc_his);
+                    if (rHis) {
+                        for (const prop of calcFields) {
+                            d[`r_${prop}_${r.audit_order}`] = rHis[prop];
+                        }
+                    }
+                    for (const prop of calcFields) {
+                        d[`his_${prop}`].push(rHis ? rHis[prop] : 0);
+                    }
+                }
+            }
+        }
+    }
+    async getCostStageBookCompare() {
+        const ledger = await this.ctx.service.costStageLedger.getReadData(this.ctx.costStage.relaStage);
+        ledger.forEach(l => { delete l.postil; delete l.memo; });
+        const book = await this.ctx.service.costStageBook.getCompareData(this.ctx.costStage);
+        this.ctx.helper.assignRelaData(ledger, [
+            { data: book, fields: ['pre_in_tp', 'pre_in_excl_tax_tp', 'calc_his', 'postil', 'memo'], prefix: '', relaId: 'ledger_id' },
+            { data: book, fields: ['id'], prefix: 'book_', relaId: 'ledger_id' },
+        ]);
+        const roles = await this.ctx.service.costStageAudit.getViewFlow(this.ctx.costStage);
+        this._analysisCompareData(ledger, roles);
+        const setting = {
+            id: 'tree_id',
+            pid: 'tree_pid',
+            order: 'tree_order',
+            level: 'tree_level',
+            isLeaf: 'tree_is_leaf',
+            fullPath: 'tree_full_path',
+            rootId: -1,
+            calcFields: ['in_tp', 'in_excl_tax_tp']
+        };
+        for(const r of roles) {
+            for (const f of calcFields) {
+                setting.calcFields.push(`r_${f}_${r.audit_order}`);
+            }
+        }
+        const compareTree = new Ledger.billsTree(this.ctx, setting);
+        compareTree.loadDatas(ledger);
+        compareTree.calculateAll();
+        return compareTree.getDefaultDatas();
+    }
+    async getCostStageBookDetailCompare() {
+        const detail = await this.ctx.service.costStageLedger.getReadData(this.ctx.costStage.relaStage);
+        detail.forEach(l => { delete l.postil; delete l.memo; });
+        const bookDetail = await this.ctx.service.costStageBook.getCompareData(this.ctx.costStage);
+        this.ctx.helper.assignRelaData(detail, [
+            { data: bookDetail, fields: ['pre_in_tp', 'pre_in_excl_tax_tp', 'calc_his', 'postil', 'memo'], prefix: '', relaId: 'detail_id' },
+        ]);
+        const roles = await this.ctx.service.costStageAudit.getViewFlow(this.ctx.costStage);
+        this._analysisCompareData(detail, roles);
+        return detail;
+    }
+
+    getCommonData(params, tableName, fields, customDefine, customSelect) {
+        switch (tableName) {
+            case 'mem_project':
+                return this.ctx.service.project.getDataByCondition({ id: this.ctx.session.sessionProject.id });
+            case 'mem_tender':
+                return [this.ctx.tender.data];
+            case 'mem_tender_info':
+                return [this.ctx.tender.info];
+            case 'mem_cost_stage':
+                return [this.ctx.costStage];
+            case 'mem_cost_stage_audit':
+                return this.ctx.service.costStageAudit.getFinalUniqAuditors(this.ctx.costStage);
+            case 'mem_cost_stage_book':
+                return this.getCostStageBook();
+            case 'mem_cost_stage_book_detail':
+                return this.getCostStageBookDetail();
+            case 'mem_cost_stage_book_compare':
+                return this.getCostStageBookCompare();
+            case 'mem_cost_stage_book_detail_compare':
+                return this.getCostStageBookDetailCompare();
+            default:
+                return [];
+        }
+    }
+}
+
+module.exports = rptMemChange;

+ 159 - 0
app/lib/rm/cost_ledger_stage.js

@@ -0,0 +1,159 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const RptMemBase = require('./base');
+const bindData = {};
+const Ledger = require('../ledger');
+const calcFields = ['pay_tp', 'cut_tp', 'yf_tp', 'sf_tp', 'yf_excl_tax_tp', 'sf_excl_tax_tp'];
+
+class rptMemChange extends RptMemBase {
+    constructor(ctx) {
+        super(ctx, bindData);
+    }
+
+    async doCheckCostStage(stage_id) {
+        if (this.ctx.costStage) return;
+        this.ctx.costStage = await this.ctx.service.costStage.getStage(stage_id);
+        await this.ctx.service.costStage.doCheckStage(this.ctx.costStage);
+    }
+
+    async doCheckTender(tenderId) {
+        if (this.ctx.tender) return;
+        this.ctx.tender = { id: tenderId };
+        this.ctx.tender.data = await this.ctx.service.tender.getTender(tenderId);
+        this.ctx.tender.info = await this.ctx.service.tenderInfo.getTenderInfo(tenderId);
+    }
+
+    async doBeforeLoadReport(params) {
+        await this.doCheckCostStage(params.cost_stage_id);
+        await this.doCheckTender(this.ctx.costStage.tid);
+    }
+
+    async getCostStageLedger() {
+        const bills = this.ctx.costStage.readOnly
+            ? await this.ctx.service.costStageLedger.getReadData(this.ctx.costStage)
+            : await this.ctx.service.costStageLedger.getEditData(this.ctx.costStage);
+        const setting = {
+            id: 'tree_id',
+            pid: 'tree_pid',
+            order: 'tree_order',
+            level: 'tree_level',
+            isLeaf: 'tree_is_leaf',
+            fullPath: 'tree_full_path',
+            rootId: -1,
+            calcFields: ['pre_pay_tp', 'pre_cur_tp', 'pre_yf_tp', 'pre_sf_tp', 'pre_yf_excl_tax_tp', 'pre_sf_excl_tax_tp', 'pay_tp', 'cur_tp', 'yf_tp', 'sf_tp', 'yf_excl_tax_tp', 'sf_excl_tax_tp'],
+        };
+        const billsTree = new Ledger.billsTree(this.ctx, setting);
+        billsTree.loadDatas(bills);
+        billsTree.calculateAll();
+        return billsTree.getDefaultDatas();
+    }
+    async getCostStageDetail() {
+        const detail = this.ctx.costStage.readOnly
+            ? await this.ctx.service.costStageDetail.getReadData(this.ctx.costStage)
+            : await this.ctx.service.costStageDetail.getEditData(this.ctx.costStage);
+        return detail;
+    }
+    _analysisCompareData(datas, roles) {
+        const findHis = function(role, history) {
+            let his = null;
+            for (const h of history) {
+                if (h.times < role.times || (h.audit_times === role.audit_times && h.active_order <= role.active_order)) {
+                    his = h;
+                } else {
+                    break;
+                }
+            }
+            return his;
+        };
+        for (const d of datas) {
+            if (!d.tree_is_leaf) continue;
+            for (const prop of calcFields) {
+                d[`his_${prop}`] = [];
+            }
+            d.calc_his.sort((x, y) => { return x.audit_times === y.audit_times ? x.active_order - y.active_order : x.audit_times - y.audit_times; });
+            for (const r of roles) {
+                if (r.latest) {
+                    for (const prop of calcFields) {
+                        d[`r_${prop}_${r.audit_order}`] = d[prop];
+                        d[`his_${prop}`].push(d[prop]);
+                    }
+                } else {
+                    const rHis = findHis(r, d.calc_his);
+                    if (rHis) {
+                        for (const prop of calcFields) {
+                            d[`r_${prop}_${r.audit_order}`] = rHis[prop];
+                        }
+                    }
+                    for (const prop of calcFields) {
+                        d[`his_${prop}`].push(rHis ? rHis[prop] : 0);
+                    }
+                }
+            }
+        }
+    }
+    async getCostStageLedgerCompare() {
+        const bills = await this.ctx.service.costStageLedger.getCompareData(this.ctx.costStage);
+        const roles = await this.ctx.service.costStageAudit.getViewFlow(this.ctx.costStage);
+        this._analysisCompareData(bills, roles);
+        const setting = {
+            id: 'tree_id',
+            pid: 'tree_pid',
+            order: 'tree_order',
+            level: 'tree_level',
+            isLeaf: 'tree_is_leaf',
+            fullPath: 'tree_full_path',
+            rootId: -1,
+            calcFields: ['pre_pay_tp', 'pre_cut_tp', 'pre_yf_tp', 'pre_sf_tp', 'pre_yf_excl_tax_tp', 'pre_sf_excl_tax_tp']
+        };
+        for(const r of roles) {
+            for (const f of calcFields) {
+                setting.calcFields.push(`r_${f}_${r.audit_order}`);
+            }
+        }
+        const compareTree = new Ledger.billsTree(this.ctx, setting);
+        compareTree.loadDatas(bills);
+        compareTree.calculateAll();
+        return compareTree.getDefaultDatas();
+    }
+    async getCostStageDetailCompare() {
+        const detail = await this.ctx.service.costStageDetail.getCompareData(this.ctx.costStage);
+        const roles = await this.ctx.service.costStageAudit.getViewFlow(this.ctx.costStage);
+        this._analysisCompareData(detail, roles);
+        return detail;
+    }
+
+    getCommonData(params, tableName, fields, customDefine, customSelect) {
+        switch (tableName) {
+            case 'mem_project':
+                return this.ctx.service.project.getDataByCondition({ id: this.ctx.session.sessionProject.id });
+            case 'mem_tender':
+                return [this.ctx.tender.data];
+            case 'mem_tender_info':
+                return [this.ctx.tender.info];
+            case 'mem_cost_stage':
+                return [this.ctx.costStage];
+            case 'mem_cost_stage_audit':
+                return this.ctx.service.costStageAudit.getFinalUniqAuditors(this.ctx.costStage);
+            case 'mem_cost_stage_ledger':
+                return this.getCostStageLedger();
+            case 'mem_cost_stage_detail':
+                return this.getCostStageDetail();
+            case 'mem_cost_stage_ledger_compare':
+                return this.getCostStageLedgerCompare();
+            case 'mem_cost_stage_detail_compare':
+                return this.getCostStageDetailCompare();
+            default:
+                return [];
+        }
+    }
+}
+
+module.exports = rptMemChange;

+ 40 - 0
app/public/js/pos_calc_tmpl.js

@@ -335,6 +335,7 @@ $(document).ready(() => {
                     },
                 ],
             };
+            this._copyTemplateId = '';
 
             this.initSpread();
             this.initOtherEvent();
@@ -348,10 +349,29 @@ $(document).ready(() => {
             SpreadJsObj.addDeleteBind(this.spread, this.deletePress);
         }
         initOtherEvent() {
+            const self = this;
             // 增删上下移升降级
             $('a[name="template-opr"]').click(function () {
                 templateObj.baseOpr(this.getAttribute('type'));
             });
+            $('#copy-template').click(function() {
+                const template = SpreadJsObj.getSelectObject(self.sheet);
+                if (!template) return;
+                self._copyTemplateId = template.id;
+                toastr.success('复制成功');
+            });
+            $('#paste-template').click(function() {
+                if (!self._copyTemplateId) {
+                    toastr.warning('请先复制模板');
+                    return;
+                }
+                const node = SpreadJsObj.getSelectObject(folderObj.sheet);
+                if (!node) {
+                    toastr.warning('请先选择您要粘贴模板的分类');
+                    return;
+                }
+                self.baseOpr('copy');
+            })
         }
         loadData(datas) {
             this.data.loadDatas(datas);
@@ -389,6 +409,11 @@ $(document).ready(() => {
                     templateObj.data.updateDatas({ add: result.add });
                     templateObj.refreshSheet();
                 });
+            } else if (type === 'copy') {
+                postData('save', {copy: { id: self._copyTemplateId, folder_id: self.folder.id }, type: 'posCalc'}, function(result) {
+                    templateObj.data.updateDatas({ add: result.copy });
+                    templateObj.refreshSheet();
+                });
             }
         }
         moveTemplate(templateIds, target, fun) {
@@ -798,6 +823,21 @@ $(document).ready(() => {
             }
             return this.colSetData;
         }
+        export() {
+            window.open(`/template/ctd?tid=${this.template.id}`);
+        }
+        import() {
+            const self = this;
+            BaseImportFile.show({
+                validList: ['.ctd'],
+                url: `/template/ctd/load?tid=${this.template.id}`,
+                afterImport: function (result) {
+                    self.template.col_set = result.update.col_set;
+                    self.template.multi_header = result.update.multi_header;
+                    self.reset();
+                }
+            });
+        }
     }
     const detailObj = new TemplateDetailObj();
 

+ 30 - 0
app/service/calc_tmpl.js

@@ -497,12 +497,42 @@ module.exports = app => {
             await this.db.updateRows(this.tableName, updateData);
             return updateData;
         }
+        async _copyTemplate(id, folder_id) {
+            const org = await this.getDataById(id);
+            if (!org || org.pid !== this.ctx.session.sessionProject.id) throw '复制的模板不存在,请刷新后重试';
+
+            const insertData = {
+                id: this.uuid.v4(),
+                pid: this.ctx.session.sessionProject.id,
+                folder_id: folder_id,
+                name: org.name,
+                type: org.type,
+                create_user_id: this.ctx.session.sessionUser.accountId,
+                update_user_id: this.ctx.session.sessionUser.accountId,
+                col_set: org.col_set,
+                spread_cache: org.spread_cache,
+                sub_spread_cache: org.sub_spread_cache,
+                field_cache: org.field_cache,
+                decimal: org.decimal,
+                calc_expr: org.calc_expr,
+                calc_order: org.calc_order,
+                spec_set: org.spec_set,
+                spec_rela: org.spec_rela,
+                multi_header: org.multi_header,
+            };
+            await this.db.insert(this.tableName, insertData);
+
+            const result = await this.getTemplate(insertData.id);
+            result.user_name = this.ctx.session.sessionUser.name;
+            return result;
+        }
         async saveTemplate(data) {
             const result = {};
             if (data.add) result.add = await this._addTemplate(data.add.name, data.type, data.add.folder_id);
             if (data.del) result.del = await this._delTemplate(data.del);
             if (data.update) result.update = await this._saveTemplate(data.update.id, data.update);
             if (data.move) result.move = await this._moveTemplate(data.move, data.folder_id);
+            if (data.copy) result.copy = await this._copyTemplate(data.copy.id, data.copy.folder_id);
             return result;
         }
         async reCalcTemplate(id, force) {

+ 24 - 0
app/service/report.js

@@ -821,6 +821,30 @@ module.exports = app => {
             return rptPhasePay.getReportData(params, sourceFilters, memFieldKeys, customDefine, customSelect);
         }
 
+        // params = { tender_id: int, cost_stage_id: uuid }
+        async cost_ledger(params, sourceFilters, memFieldKeys, customDefine, customSelect) {
+            const RptCostStage = require('../lib/rm/cost_ledger_stage');
+            const rptCostStage = new RptCostStage(this.ctx);
+
+            return rptCostStage.getReportData(params, sourceFilters, memFieldKeys, customDefine, customSelect);
+        }
+
+        // params = { tender_id: int, cost_stage_id: uuid }
+        async cost_book(params, sourceFilters, memFieldKeys, customDefine, customSelect) {
+            const RptCostStage = require('../lib/rm/cost_book_stage');
+            const rptCostStage = new RptCostStage(this.ctx);
+
+            return rptCostStage.getReportData(params, sourceFilters, memFieldKeys, customDefine, customSelect);
+        }
+
+        // params = { tender_id: int, cost_stage_id: uuid }
+        async cost_analysis(params, sourceFilters, memFieldKeys, customDefine, customSelect) {
+            const RptCostStage = require('../lib/rm/cost_analysis_stage');
+            const rptCostStage = new RptCostStage(this.ctx);
+
+            return rptCostStage.getReportData(params, sourceFilters, memFieldKeys, customDefine, customSelect);
+        }
+
         async getReportData(source_type, params, sourceFilters, memFieldKeys, customDefine, customSelect) {
             this.clearReportCache();
             const sourceType = sourceTypeConst.sourceTypeData.find(x => { return x.id === source_type; });

+ 4 - 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>
@@ -46,6 +46,8 @@
                                     <li class="nav-item mt-1" id="template-ctrl">
                                         <a href="javascript: void(0);" name="template-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="template-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);" id="copy-template" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="复制模板"><i class="fa fa-copy" aria-hidden="true"></i></a>
+                                        <a href="javascript: void(0);" id="paste-template" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="粘贴模板"><i class="fa fa-paste" aria-hidden="true"></i></a>
                                         <button class="btn btn-sm btn-light text-primary" href="#rela-no-folder" data-toggle="modal" data-target="#rela-no-folder"> 导入未分类模板</button>
                                     </li>
                                 </ul>