فهرست منبع

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

Tony Kang 11 ماه پیش
والد
کامیت
29594930eb
44فایلهای تغییر یافته به همراه884 افزوده شده و 198 حذف شده
  1. 6 0
      app/const/spread.js
  2. 22 1
      app/controller/file_controller.js
  3. 18 14
      app/controller/ledger_controller.js
  4. 10 15
      app/controller/stage_controller.js
  5. 12 3
      app/controller/tender_controller.js
  6. 1 0
      app/extend/context.js
  7. 16 0
      app/lib/spread_setting.js
  8. 3 0
      app/public/css/main.css
  9. 10 15
      app/public/js/change.js
  10. 1 1
      app/public/js/change_apply_information.js
  11. 12 1
      app/public/js/change_company.js
  12. 5 2
      app/public/js/change_information_set.js
  13. 1 1
      app/public/js/change_plan_information.js
  14. 1 1
      app/public/js/change_project_information.js
  15. 221 21
      app/public/js/file_detail.js
  16. 190 0
      app/public/js/filing_template.js
  17. 1 1
      app/public/js/ledger_check.js
  18. 25 22
      app/public/js/shenpi.js
  19. 1 0
      app/router.js
  20. 10 0
      app/service/file.js
  21. 47 12
      app/service/filing.js
  22. 54 1
      app/service/filing_template.js
  23. 5 20
      app/service/jpc_report.js
  24. 15 2
      app/service/ledger_audit.js
  25. 5 0
      app/service/report.js
  26. 2 2
      app/service/report_memory.js
  27. 13 3
      app/service/rpt_gather_memory.js
  28. 3 2
      app/service/shenpi_audit.js
  29. 1 0
      app/service/stage.js
  30. 2 2
      app/service/stage_yjcl.js
  31. 2 2
      app/service/sub_project.js
  32. 1 0
      app/service/tender_info.js
  33. 1 1
      app/view/change/information.ejs
  34. 1 1
      app/view/change/relation.ejs
  35. 77 42
      app/view/file/file.ejs
  36. 2 2
      app/view/file/file_modal.ejs
  37. 5 0
      app/view/file/template.ejs
  38. 21 0
      app/view/file/template_modal.ejs
  39. 1 1
      app/view/ledger/explode.ejs
  40. 2 1
      app/view/ledger/explode_modal.ejs
  41. 40 0
      app/view/shares/select_file_modal.ejs
  42. 0 1
      app/view/tender/shenpi.ejs
  43. 8 1
      config/web.js
  44. 10 4
      sql/update.sql

+ 6 - 0
app/const/spread.js

@@ -790,6 +790,11 @@ const SpreadSpec = {
         ],
     }
 };
+const SpecSpreadColFields = [
+    { key: 'ex_memo1', fields: ['ex_memo1'] },
+    { key: 'ex_memo3', fields: ['ex_memo2'] },
+    { key: 'ex_memo3', fields: ['ex_memo3'] },
+];
 
 const withCl = {
     ledger: {
@@ -1414,6 +1419,7 @@ module.exports = {
     ProjectSpreadTemplate,
     BaseSpreadColSetting,
     SpreadSpec,
+    SpecSpreadColFields,
     withoutClReplace,
     withCl,
     withoutCl,

+ 22 - 1
app/controller/file_controller.js

@@ -385,7 +385,7 @@ module.exports = app => {
                 if (!renderData.template) throw '查看的资料模板不存在';
 
                 renderData.templateData = await ctx.service.filingTemplate.getData(renderData.template.id);
-                await this.layout('file/template.ejs', renderData);
+                await this.layout('file/template.ejs', renderData, 'file/template_modal.ejs');
             } catch (err) {
                 ctx.log(err);
                 ctx.session.postError = err.toString();
@@ -450,6 +450,10 @@ module.exports = app => {
                 } else if (data.updateType === 'move') {
                     if (!data.id || !(data.tree_order >= 0)) throw '数据错误';
                     result = await ctx.service.filingTemplate.move(ctx.params.id, data);
+                } else if (data.updateType === 'import') {
+                    result = await ctx.service.filingTemplate.import(ctx.params.id, data.data);
+                } else if (data.updateType === 'multi' ) {
+                    result = await ctx.service.filingTemplate.multiUpdate(ctx.params.id, data.data);
                 }
                 ctx.body = { err: 0, msg: '', data: result };
             } catch (err) {
@@ -457,6 +461,23 @@ module.exports = app => {
                 ctx.ajaxErrorBody(err, '修改失败');
             }
         }
+
+        async search(ctx) {
+            try {
+                const limit = 1000;
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.filing_type || !data.keyword) throw '数据错误';
+                const validFilingType = [];
+                for (const f of data.filing_type) {
+                    if (ctx.subProject.permission.filing_type === 'all' || ctx.subProject.permission.filing_type.indexOf(f) >= 0) validFilingType.push(f);
+                }
+                const result = await ctx.service.file.search(validFilingType, data.keyword, limit);
+                ctx.body = { err: 0, msg: '', data: { list: result, limit } };
+            } catch(err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '搜索文件失败');
+            }
+        }
     }
 
     return BudgetController;

+ 18 - 14
app/controller/ledger_controller.js

@@ -66,7 +66,7 @@ module.exports = app => {
                 (tender.ledger_status === auditConst.status.checked && upPermission);
         }
 
-        _getLedgerColumn(sjsRela) {
+        async _getLedgerColumn() {
             const tender = this.ctx.tender;
             const ledgerColumn = [
                 'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
@@ -77,12 +77,13 @@ module.exports = app => {
             if (tender.info.display.ledger.clQty) ledgerColumn.push('sjcl_qty', 'qtcl_qty', 'sjcl_expr', 'qtcl_expr', 'sjcl_tp', 'qtcl_tp');
             const posColumn = ['id', 'tid', 'lid', 'name', 'position', 'porder', 'sgfh_qty', 'sgfh_expr', 'add_stage_order', 'drawing_code', 'quantity'];
             if (tender.info.display.ledger.clQty) posColumn.push('sjcl_qty', 'qtcl_qty', 'sjcl_expr', 'qtcl_expr');
-            for (const field of sjsRela.ledgerCol) {
-                if (field.show) {
-                    ledgerColumn.push(field.field);
-                    posColumn.push(field.field);
-                }
+
+            const extraFields = await spreadSetting.getExtraFields(this.ctx, tender.id);
+            if (extraFields.length > 0) {
+                ledgerColumn.push(...extraFields);
+                posColumn.push(...extraFields);
             }
+
             return [ledgerColumn, posColumn];
         }
 
@@ -427,15 +428,18 @@ module.exports = app => {
          */
         async loadExplodeData(ctx) {
             try {
-                const sjsRela = await ctx.service.project.getTenderSjsRela(ctx.session.sessionProject.id, ctx.tender.info.display.exMemo);
-                const [ledgerColumn, posColumn] = this._getLedgerColumn(sjsRela);
-                const ledgerData = ctx.tender.ledgerReadOnly && ctx.tender.his
-                    ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.bills_file)
-                    : await ctx.service.ledger.getAllDataByCondition({ columns: ledgerColumn, where: { tender_id: ctx.tender.id } });
+                const [ledgerColumn, posColumn] = await this._getLedgerColumn();
+                // const ledgerData = ctx.tender.ledgerReadOnly && ctx.tender.his
+                //     ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.bills_file)
+                //     : await ctx.service.ledger.getAllDataByCondition({ columns: ledgerColumn, where: { tender_id: ctx.tender.id } });
+                // const posData = this.ctx.tender.data.measure_type === measureType.tz.value
+                //     ? (ctx.tender.ledgerReadOnly && ctx.tender.his
+                //         ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.pos_file)
+                //         : await ctx.service.pos.getPosData({tid: ctx.tender.id}, posColumn))
+                //     : [];
+                const ledgerData = await ctx.service.ledger.getAllDataByCondition({ columns: ledgerColumn, where: { tender_id: ctx.tender.id } });
                 const posData = this.ctx.tender.data.measure_type === measureType.tz.value
-                    ? (ctx.tender.ledgerReadOnly && ctx.tender.his
-                        ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.pos_file)
-                        : await ctx.service.pos.getPosData({tid: ctx.tender.id}, posColumn))
+                    ? await ctx.service.pos.getPosData({tid: ctx.tender.id}, posColumn)
                     : [];
                 const ancillaryGclData = this.ctx.tender.data.measure_type === measureType.tz.value
                     ? await ctx.service.ancillaryGcl.getAllDataByCondition({ where: { tid: ctx.tender.id } })

+ 10 - 15
app/controller/stage_controller.js

@@ -215,7 +215,7 @@ module.exports = app => {
             return surplus;
         }
 
-        _getLedgerColumn(sjsRela) {
+        async _getLedgerColumn() {
             const tender = this.ctx.tender;
             this.ledgerColumn = [
                 'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
@@ -237,16 +237,12 @@ module.exports = app => {
             if (this.ctx.session.sessionProject.dagl) this.posExtraColumn.push('dagl_status', 'dagl_url', 'dagl_limit');
             this.posMemoColumn = [];
 
-            if (!sjsRela) return;
-            if (sjsRela) {
-                for (const field of sjsRela.ledgerCol) {
-                    if (field.show) {
-                        this.ledgerMemoColumn.push(field.field);
-                        this.ledgerColumn.push(field.field);
-                        this.posColumn.push(field.field);
-                        this.posMemoColumn.push(field.field);
-                    }
-                }
+            const extraFields = await spreadSetting.getExtraFields(this.ctx, tender.id, 'stage');
+            if (extraFields.length > 0) {
+                this.ledgerColumn.push(...extraFields);
+                this.ledgerMemoColumn.push(...extraFields);
+                this.posColumn.push(...extraFields);
+                this.posMemoColumn.push(...extraFields);
             }
         }
 
@@ -362,8 +358,7 @@ module.exports = app => {
                 const filter = data.filter.split(';');
                 const responseData = { err: 0, msg: '', data: {}, hpack: [] };
                 const hpack = true;
-                const sjsRela = await this.ctx.service.project.getTenderSjsRela(ctx.session.sessionProject.id, ctx.tender.info.display.exMemo);
-                this._getLedgerColumn(sjsRela);
+                await this._getLedgerColumn();
                 for (const f of filter) {
                     switch (f) {
                         case 'ledger':
@@ -447,7 +442,7 @@ module.exports = app => {
         async check(ctx) {
             try {
                 const helper = this.ctx.helper;
-                this._getLedgerColumn();
+                await this._getLedgerColumn();
                 const ledgerData = await this._getStageLedgerData(ctx);
                 ledgerData.forEach(x => {
                     x.end_qc_minus_qty = helper.add(x.pre_qc_minus_qty, x.qc_minus_qty);
@@ -1586,7 +1581,7 @@ module.exports = app => {
 
         async loadBwtz(ctx) {
             try {
-                this._getLedgerColumn();
+                await this._getLedgerColumn();
                 const data = ctx.request.body.data ? JSON.parse(ctx.request.body.data) : {};
                 const ledgerData = await this._getStageLedgerData(ctx);
                 const posData = await this._getStagePosData(ctx);

+ 12 - 3
app/controller/tender_controller.js

@@ -1021,13 +1021,22 @@ module.exports = app => {
                 // }
                 await ctx.service.tenderInfo.saveTenderInfo(ctx.tender.id, { shenpi: postData });
                 let auditList = [];
+                let groupList = [];
                 if (data.status === shenpiConst.sp_status.gdspl) {
-                    const group = await ctx.service.shenpiGroup.getGroupBySelect(ctx.tender.id, shenpiConst.sp_type[data.code]);
-                    auditList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.tender.id, shenpiConst.sp_type[data.code], data.status, group ? group.id : 0);
+                    groupList = await ctx.service.shenpiGroup.getGroupList(ctx.tender.id, shenpiConst.sp_type[data.code]) || [];
+                    if (groupList && groupList.length > 0) {
+                        for (const group of groupList) {
+                            if (group.change_type) group.change_type = JSON.parse(group.change_type);
+                            group.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.tender.id, shenpiConst.sp_type[data.code], data.status, group.id);
+                            if (group.is_select) auditList = group.auditGroupList;
+                        }
+                    } else {
+                        auditList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.tender.id, shenpiConst.sp_type[data.code], data.status);
+                    }
                 } else if (data.status === shenpiConst.sp_status.gdzs) {
                     auditList = await ctx.service.shenpiAudit.getAudit(ctx.tender.id, shenpiConst.sp_type[data.code], data.status);
                 }
-                ctx.body = { err: 0, msg: '', data: auditList };
+                ctx.body = { err: 0, msg: '', data: { auditList, groupList } };
             } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '保存审批流程设置失败');

+ 1 - 0
app/extend/context.js

@@ -10,6 +10,7 @@
 
 const moment = require('moment');
 const messageType = require('../const/message_type');
+const path = require('path');
 
 module.exports = {
     moment,

+ 16 - 0
app/lib/spread_setting.js

@@ -159,8 +159,24 @@ const getStageGatherSpreadSetting = async function (ctx, tid) {
     return [gcl, leafXmj, gatherLeafXmj];
 };
 
+const getExtraFields = async function(ctx, tid, type = 'ledger') {
+    let tender = tid ? await getCtxTender(ctx, tid) : ctx.tender;
+
+    const projectSpread = await ctx.service.projectSpread.getProjectSpread(ctx.session.sessionProject.id, tender.info.s_type);
+    const prefix = tender.data.measure_type === measureType.tz.value ? 'tz' : 'gcl';
+    const setting = projectSpread[`${prefix}_${type}_set`];
+
+    const result = [];
+    for (const col of spreadConst.SpecSpreadColFields) {
+        const relaSet = setting.find(x => { return x.key === col.key; });
+        if (relaSet && relaSet.valid) result.push(...col.fields);
+    }
+    return result;
+};
+
 module.exports = {
     getLedgerSpreadSetting,
     getStageSpreadSetting,
     getStageGatherSpreadSetting,
+    getExtraFields,
 };

+ 3 - 0
app/public/css/main.css

@@ -2155,6 +2155,9 @@ animation:shake 1s .2s ease both;}
 .form-control-width{
     min-width: 450px;
 }
+.form-control-s-width{
+    min-width: 280px;
+}
 .vertical-middle{
     display: flex;
     margin: auto;

+ 10 - 15
app/public/js/change.js

@@ -13,7 +13,7 @@ function getNewCode() {
         if (code !== '') {
             $('#bj-code').val(code);
             if (openChangePlan && changePlanList && _.findIndex(changePlanList, { code: code }) !== -1) {
-                $('#plan-code').val(code).trigger('change');;
+                $('#plan-code').val(code).trigger('change');
             }
         }
     });
@@ -137,13 +137,7 @@ class codeRuleSet {
         });
     }
 }
-/**
- * 期计量 - 期列表页面 js
- *
- * @author Mai
- * @date 2018/12/7
- * @version
- */
+
 const getGroupAuditHtml = function (group) {
     return group.map(u => { return `<small class="d-inline-block text-dark mx-1" title="${u.role}" data-auditorId="${u.aid}">${u.name}</small>`; }).join('');
 };
@@ -518,11 +512,12 @@ $(document).ready(() => {
             $('#plan-code').val(code).trigger('change');
         }
     });
-
-    $('#plan-code').select2({
-        language: 'zh-CN',
-        theme: 'bootstrap4',
-        selectOnClose: true,
-        // width: '150',
-    });
+    if (openChangePlan) {
+        $('#plan-code').select2({
+            language: 'zh-CN',
+            theme: 'bootstrap4',
+            selectOnClose: true,
+            // width: '150',
+        });
+    }
 });

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

@@ -839,7 +839,7 @@ function validateFiles(files) {
             toastr.error('文件大小限制为30MB');
             return false
         }
-        if (whiteList.indexOf('.' + file.ext) === -1) {
+        if (whiteList.indexOf('.' + file.ext.toLowerCase()) === -1) {
             toastr.error('请上传正确的格式文件');
             return false
         }

+ 12 - 1
app/public/js/change_company.js

@@ -46,7 +46,18 @@ $(document).ready(() => {
                 $('#editcompany').modal('hide');
                 toastr.success('变更单位已更新');
                 $('#updatecompany').attr('disabled',false);
-                changeInfo.company = $('#company').val();
+                let hadChange = true;
+                $('#company option').each(function(){
+                    if ($(this).val() === changeInfo.company) {
+                        hadChange = false;
+                        return;
+                    }
+                });
+                if (hadChange) {
+                    changeInfo.company = $('#company').val();
+                } else {
+                    $('#company').val(changeInfo.company);
+                }
                 judgeChange();
             });
         }else{

+ 5 - 2
app/public/js/change_information_set.js

@@ -1153,7 +1153,8 @@ $(document).ready(() => {
                         return changeOrder === 0;
                     },
                     callback: function (key, opt) {
-                        $('#addlist').modal('show');
+                        // $('#addlist').modal('show');
+                        $('#set-site-btn').click();
                     },
                 },
                 'createAdd': {
@@ -1206,7 +1207,8 @@ $(document).ready(() => {
                     //     }
                     // },
                     callback: function (key, opt) {
-                        $('#addlist').modal('show');
+                        // $('#addlist').modal('show');
+                        $('#set-site-btn').click();
                     },
                 },
                 'createAdd1': {
@@ -1932,6 +1934,7 @@ $(document).ready(() => {
         changeInfo.basis = changeInfo.basis.replace(/<br><br>/g, '\r\n');
         changeInfo.expr = changeInfo.expr.replace(/<br><br>/g, '\r\n');
         changeInfo.memo = changeInfo.memo.replace(/<br><br>/g, '\r\n');
+        changeInfo.company = $('#change_form select[name="company"]').val();
         // 后改为br
         // 更新至服务器
         postData(window.location.pathname + '/save', { type:'info', updateData: changeInfo }, function (result) {

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

@@ -988,7 +988,7 @@ function validateFiles(files) {
             toastr.error('文件大小限制为30MB');
             return false
         }
-        if (whiteList.indexOf('.' + file.ext) === -1) {
+        if (whiteList.indexOf('.' + file.ext.toLowerCase()) === -1) {
             toastr.error('请上传正确的格式文件');
             return false
         }

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

@@ -202,7 +202,7 @@ function validateFiles(files) {
             toastr.error('文件大小限制为30MB');
             return false
         }
-        if (whiteList.indexOf('.' + file.ext) === -1) {
+        if (whiteList.indexOf('.' + file.ext.toLowerCase()) === -1) {
             toastr.error('请上传正确的格式文件');
             return false
         }

+ 221 - 21
app/public/js/file_detail.js

@@ -1,4 +1,5 @@
 $(document).ready(function() {
+    let fileSearch;
     autoFlashHeight();
     $('#filing').height($(".sjs-height-0").height() - $('#add-slibing').parent().parent().height() - 10);
     class FilingObj {
@@ -288,34 +289,35 @@ $(document).ready(function() {
         }
         delFiles(files, callback) {
             postData('file/del', { del: files }, async function(data) {
+                const relaFiling = data.filing.id === filingObj.curFiling.source_node.id
+                    ? filingObj.curFiling : filingObj.findFiling(data.filing.id);
                 for (const id of data.del) {
-                    const fIndex = filingObj.curFiling.source_node.files.findIndex(x => { return x.id === id });
-                    if (fIndex >= 0) filingObj.curFiling.source_node.files.splice(fIndex, 1);
+                    const fIndex = relaFiling.source_node.files.findIndex(x => { return x.id === id });
+                    if (fIndex >= 0) relaFiling.source_node.files.splice(fIndex, 1);
+                    fileSearch.removeSearchFile(id);
+                }
+                filingObj.updateFilingFileCount(relaFiling, data.filing.file_count);
+                await filingObj.loadFiles(relaFiling, filingObj.curPage);
+                if (data.filing.id === filingObj.curFiling.source_node.id) {
+                    filingObj.refreshPages();
+                    filingObj.refreshFilesTable();
                 }
-                filingObj.updateFilingFileCount(filingObj.curFiling, data.filing.file_count);
-                await filingObj.loadFiles(filingObj.curFiling, filingObj.curPage);
-                filingObj.refreshPages();
-                filingObj.refreshFilesTable();
                 if (callback) callback();
             });
         }
-        renameFile(fileId, filename) {
+        renameFile(file, filename) {
             const self = this;
-            const file = filingObj.curFiling.source_node.files.find(x => { return x.id === fileId });
-            if (!file) return;
-
-            const td = $(`td[fid=${fileId}]`);
-            if (filename === file.filename + file.fileext) {
-                td.html(this._getFileNameHtml(file));
-                return;
-            }
-
-            postData('file/save', { id: fileId, filename }, function(data) {
-                file.filename = data.filename;
-                file.fileext = data.fileext;
-                td.html(self._getFileNameHtml(file));
+            const td = $(`td[fid=${file.id}]`);
+            postData('file/save', { id: file.id, filename }, function(data) {
+                const relaFiling = filingObj.findFiling(file.filing_id);
+                const relaFile = relaFiling.source_node.files.find(x => { return x.id === file.id });
+                relaFile.filename = data.filename;
+                relaFile.fileext = data.fileext;
+                td.html(self._getFileNameHtml(relaFile));
+                fileSearch.renameSearchFile(file.id, data);
             }, function() {
                 td.html(self._getFileNameHtml(file));
+                fileSearch.renameSearchFile(file.id);
             });
         }
         relaFiles(files, callback) {
@@ -346,6 +348,9 @@ $(document).ready(function() {
             }
             setLocalCache(this.curFilingKey, filingObj.curFiling.id);
         }
+        findFiling(id) {
+            return filingObj.filingTree.getNodeByParam('id', id);
+        }
         prePage() {
             if (this.curPage === 1) return;
             this.curPage = this.curPage - 1;
@@ -435,6 +440,37 @@ $(document).ready(function() {
             this._clearAllFileCache();
             this.setCurFiling(this.curFiling);
         }
+        getNodeFilingType(node) {
+            if (!node.is_fixed) return [];
+
+            const types = [];
+            if (node.children && node.children.length > 0) {
+                for (const child of node.children) {
+                    const childTypes = this.getNodeFilingType(child);
+                    if (childTypes.length > 0) types.push(...childTypes);
+                }
+            }
+            if (types.length === 0) types.push(node.filing_type);
+            return types;
+        }
+        getParentFilingType(node) {
+            const parent = this.dragTree.getParent(node);
+            if (!parent) return [];
+            if (parent.is_fixed) return [parent.filing_type];
+            return this.getParentFilingType(parent);
+        }
+        getAllFilingType() {
+            const types = [];
+            for (const node of this.dragTree.children) {
+                types.push(...this.getNodeFilingType(node))
+            }
+            return types;
+        }
+        getCurFilingType() {
+            const cur = filingObj.curFiling;
+            const node = this.dragTree.nodes.find(x => { return x.id === cur.source_node.id; });
+            return node.is_fixed ? this.getNodeFilingType(node) : this.getParentFilingType(node);
+        }
     }
     const levelTreeSetting = {
         treeId: 'filing',
@@ -662,7 +698,13 @@ $(document).ready(function() {
         const file = filingObj.curFiling.source_node.files.find(x => { return x.id === fid });
         if (!file) return;
 
-        filingObj.renameFile(fid, $('input', td).val());
+        const filename = $('input', td).val();
+        if (filename === file.filename + file.fileext) {
+            td.html(this._getFileNameHtml(file));
+            return;
+        }
+
+        filingObj.renameFile(file, filename);
     });
     $('body').on('click', "a[name=edit-file-cancel]", function() {
         const td = $(this).parent().parent().parent();
@@ -1218,6 +1260,164 @@ $(document).ready(function() {
         list: '#filing-valid',
     });
 
+    class FileSearch {
+        constructor() {
+            this.searchResult = [];
+            this.initSearch();
+        }
+        getOperateHtml(file) {
+            const locateHtml = `<a href="javascript: void(0);" class="mr-1" name="locate-search-file" fid="${file.id}"><i class="fa fa-crosshairs"></i></a>`;
+            const editHtml = file.canEdit ? `<a href="javascript: void(0);" class="mr-1" name="edit-search-file" fid="${file.id}"><i class="fa fa-pencil fa-fw"></i></a>` : '';
+            const viewHtml = file.viewpath ? `<a href="${file.viewpath}" class="mr-1" target="_blank"><i class="fa fa-eye fa-fw"></i></a>` : '';
+            const downHtml = `<a href="javascript: void(0);" onclick="AliOss.downloadFile('${file.filepath}', '${file.filename + file.fileext}')" class="mr-1"><i class="fa fa-download fa-fw"></i></a>`;
+            const delHtml = file.canEdit ? `<a href="javascript: void(0);" class="mr-1 text-danger" name="del-search-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i></a>` : '';
+            return `<div class="d-flex justify-content-between align-items-center table-file">${locateHtml}${editHtml}${viewHtml}${downHtml}${delHtml}</div>`
+        }
+        getFileHtml(file) {
+            const html = [];
+            html.push(`<tr fid="search_${file.id}">`);
+            html.push(`<td fid="search_${file.id}">${file.filename}${file.fileext}</td>`);
+            html.push(`<td class="text-center">${file.user_name}</td>`);
+            html.push(`<td class="text-center">${this.getOperateHtml(file)}</td>`);
+            html.push('</tr>');
+            return html.join('');
+        }
+        getResultHtml (result) {
+            this.searchResult = result;
+            const html = [];
+            for (const r of result) {
+                html.push(this.getFileHtml(r));
+            }
+            return html.join('');
+        }
+        getSearchFilter() {
+            const filter = $('#search-filter').val();
+            const filing_type = filter === 'all' ? filingObj.getAllFilingType() : filingObj.getCurFilingType();
+            return { filing_type };
+        }
+        getEditFileNameHtml(file) {
+            const inputHtml = `<input type="text" class="form-control form-control-sm form-control-s-width" maxlength="100" value="${file.filename + file.fileext}" fid="${file.id}">`;
+            const btnHtml = `<div class="btn-group-table" style="display: none;"><a href="javascript: void(0)" class="mr-1" name="edit-search-file-ok"><i class="fa fa-check fa-fw"></i></a><a href="javascript: void(0)" class="mr-1" name="edit-search-file-cancel"><i class="fa fa-remove fa-fw"></i></a></div>`;
+            return `<div class="d-flex justify-content-between align-items-center table-file"><div>${inputHtml}</div>${btnHtml}</div>`;
+        }
+        search() {
+            const searchData = this.getSearchFilter();
+            searchData.keyword = $('#search-keyword', '#search').val();
+            if (!searchData.keyword) {
+                toastr.warning('请输入查询的文件名称');
+                this.getResultHtml([]);
+                return;
+            }
+            postData(window.location.pathname + '/search', searchData, function(result) {
+                if (result.list.length === result.limit) toastr.warning(`最多查询${result.limit}个结果,请给出更准确的文件名称`);
+                $('#search-list').html(fileSearch.getResultHtml(result.list));
+            });
+        }
+        removeSearchFile(fid){
+            $(`tr[fid=search_${fid}]`, '#search').remove();
+            const index = this.searchResult.findIndex(x => { return x.id === fid; });
+            this.searchResult.splice(index, 1);
+        }
+        renameSearchFile(fid, data) {
+            const file = this.searchResult.find(x => { return x.id === fid; });
+            if (file) {
+                file.filename = data.filename;
+                file.fileext = data.fileext;
+                const td = $(`td[fid=search_${file.id}]`);
+                td.html(`${file.filename}${file.fileext}`);
+            }
+        }
+        initSearch() {
+            const self = this;
+            $.divResizer({
+                select: '#right-spr',
+                callback: function() {},
+            });
+            $('input', '#search').bind('keydown', function (e) {
+                if (e.keyCode == 13) self.search();
+            });
+            $('button', '#search').bind('click', () => { self.search(); });
+            $('body').on('click', "a[name=del-search-file]", function() {
+                const del = [this.getAttribute('fid')];
+                filingObj.delFiles(del);
+            });
+            $('body').on('click', "a[name=locate-search-file]", function() {
+                const fid = this.getAttribute('fid');
+                const file = self.searchResult.find(x => { return x.id === fid});
+                const filing = filingObj.findFiling(file.filing_id);
+                filingObj.filingTree.selectNode(filing);
+                filingObj.setCurFiling(filing);
+            });
+            $('body').on('click', "a[name=edit-search-file]", function() {
+                const check = $('[name=search-filename] input[fid]');
+                if (check.length > 0 && check[0].getAttribute('fid') === this.getAttribute('fid')) return;
+
+                const id = this.getAttribute('fid');
+                const file = self.searchResult.find(x => { return x.id === id });
+                $(`td[fid=search_${id}]`).html(self.getEditFileNameHtml(file));
+            });
+            $('body').on('click', "a[name=edit-search-file-ok]", function() {
+                const td = $(this).parent().parent().parent();
+                const fid = td.attr('fid').split('_')[1];
+                const file = self.searchResult.find(x => { return x.id === fid });
+                if (!file) return;
+
+                filingObj.renameFile(file, $('input', td).val());
+            });
+            $('body').on('click', "a[name=edit-search-file-cancel]", function() {
+                const td = $(this).parent().parent().parent();
+                const fid = td.attr('fid').split('_')[1];
+                const file = self.searchResult.find(x => { return x.id === fid });
+                if (!file) return;
+
+                td.html(`${file.filename}${file.fileext}`);
+            });
+        }
+    }
+    fileSearch = new FileSearch();
+    const showSideTools = function (show) {
+        const left = $('#left-view'), right = $('#right-view'), parent = left.parent();
+        if (show) {
+            right.show();
+            autoFlashHeight();
+            /**
+             * right.show()后, parent被撑开成2倍left.height, 导致parent.width减少了10px
+             * 第一次left.width调整后,parent的缩回left.height, 此时parent.width又增加了10px
+             * 故需要通过最终的parent.width再计算一次left.width
+             *
+             * Q: 为什么不通过先计算left.width的宽度,以避免计算两次left.width?
+             * A: 右侧工具栏不一定显示,当右侧工具栏显示过一次后,就必须使用parent和right来计算left.width
+             *
+             */
+                //left.css('width', parent.width() - right.outerWidth());
+                //left.css('width', parent.width() - right.outerWidth());
+            const percent = 100 - right.outerWidth() /parent.width() * 100;
+            left.css('width', percent + '%');
+        } else {
+            left.width(parent.width());
+            right.hide();
+        }
+    };
+    // 展开收起标准清单
+    $('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'));
+        } else { // 收起工具栏
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+    });
+
     // 显示层次
     (function (select) {
         $(select).click(function () {

+ 190 - 0
app/public/js/filing_template.js

@@ -117,6 +117,13 @@ $(document).ready(function() {
                 }
             });
         }
+        batchUpdateFiling(data, callback) {
+            const self = this;
+            postData(`${window.location.pathname}/update`, data, function(result) {
+                self.analysisFiling(result);
+                if (callback) callback();
+            })
+        }
     }
     const levelTreeSetting = {
         treeId: 'filing',
@@ -262,4 +269,187 @@ $(document).ready(function() {
             '</div>');
         $(`.table-file[tempId=${tempId}]`).html(html.join(''));
     });
+
+    class MultiObj {
+        constructor(setting) {
+            this.modal = $(`#${setting.modal}`);
+            this.spread = SpreadJsObj.createNewSpread($(`#${setting.spread}`)[0]);
+            this.sheet = this.spread.getActiveSheet();
+            this.spreadSetting = {
+                cols: [
+                    { title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 250, formatter: '@', readOnly: true, cellType: 'tree' },
+                    { title: '固定', colSpan: '1', rowSpan: '1', field: 'is_fixed', hAlign: 1, width: 50, cellType: 'checkbox' },
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+            };
+            SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
+            this.checkTree = createNewPathTree('base', {
+                id: 'tree_id',
+                pid: 'tree_pid',
+                order: 'order',
+                level: 'level',
+                isLeaf: 'is_leaf',
+                fullPath: 'full_path',
+                rootId: -1,
+            });
+            const self = this;
+            this.modal.bind('shown.bs.modal', function() {
+                self.spread.refresh();
+            });
+            this.spread.bind(spreadNS.Events.ButtonClicked, function(e, info) {
+                if (!info.sheet.zh_setting) return;
+                const sheet = info.sheet, cellType = sheet.getCellType(info.row, info.col);
+                if (cellType instanceof  spreadNS.CellTypes.CheckBox) {
+                    if (sheet.isEditing()) sheet.endEdit(true);
+                }
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field !== 'is_fixed') return;
+                const tree = self.checkTree;
+                const node = tree.nodes[info.row];
+                if (node.level <= 1 && node.is_fixed) {
+                    toastr.warning('顶层节点不可取消固定');
+                    SpreadJsObj.reLoadRowsData(info.sheet, [info.row]);
+                    return;
+                }
+
+                const row = [];
+                if (!node.is_fixed) {
+                    const relas = [];
+                    if (node.level > 1) {
+                        const parents = tree.getAllParents(node);
+                        for (const p of parents) {
+                            relas.push(...p.children);
+                            if (p.level === 1) relas.push(p);
+                        }
+                    } else {
+                        relas.push(node);
+                    }
+                    for (const p of relas) {
+                        p.is_fixed = true;
+                        row.push(tree.nodes.indexOf(p));
+                    }
+                } else {
+                    const parent = tree.getParent(node);
+                    const posterity = tree.getPosterity(parent);
+                    for (const p of posterity) {
+                        p.is_fixed = false;
+                        row.push(tree.nodes.indexOf(p));
+                    }
+                }
+                SpreadJsObj.reLoadRowsData(info.sheet, row);
+            });
+            $(`#${setting.modal}-ok`).click(function() {
+                try {
+                    const data = self.getMultiUpdateData();
+                    filingObj.batchUpdateFiling(data, function() {
+                        self.modal.modal('hide');
+                    });
+                } catch(err) {
+                    toastr.error(err.stack ? '保存配置数据错误' : err);
+                }
+            });
+        }
+        getMultiUpdateData() {
+            const data = [], self = this;
+            for (const [i, node] of this.checkTree.nodes.entries()) {
+                node.source_filing_type = i + 1;
+            }
+            const getUpdateData = function(children) {
+                for (const [i, node] of children.entries()) {
+                    let filing_type = node.source_filing_type;
+                    if (!node.is_fixed) {
+                        const parents = self.checkTree.getAllParents(node);
+                        parents.sort((a, b) => { return b.tree_level - a.tree_level});
+                        const fixedParent = parents.find(function(x) { return x.is_fixed; });
+                        if (!fixedParent) throw `【${node.name}】查询不到固定信息`;
+                        filing_type = fixedParent.source_filing_type;
+                    }
+                    data.push({ id: node.id, is_fixed: node.is_fixed, filing_type, tree_order: i + 1 });
+                    if (node.children) getUpdateData(node.children);
+                }
+            };
+            getUpdateData(this.checkTree.children);
+            return {updateType: 'multi', data};
+        }
+        _convertData(sourceTree) {
+            const data = [];
+            for (const node of sourceTree.nodes) {
+                const parent = node.tree_pid === '-1' ? undefined : data.find(x => { return x.id === node.tree_pid; });
+                const child = sourceTree.nodes.find(x => { return x.tree_pid === node.id; });
+                data.push({
+                    id: node.id,
+                    tree_id: data.length + 1,
+                    tree_pid: parent ? parent.tree_id : -1,
+                    order: node.tree_order + 1,
+                    level: node.tree_level,
+                    is_leaf: !child,
+                    full_path: '',
+                    name: node.name,
+                    is_fixed: node.is_fixed,
+                    filing_type: node.filing_type,
+                });
+            }
+            return data;
+        }
+        reload(sourceTree) {
+            this.checkTree.loadDatas(this._convertData(sourceTree));
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.checkTree);
+        }
+        show() {
+            this.modal.modal('show');
+        }
+        exportFile(sourceTree) {
+            const exportData = sourceTree.nodes.map(node => { return {
+                id: node.id,
+                tree_pid: node.tree_pid,
+                tree_order: node.tree_order,
+                tree_level: node.tree_level,
+                name: node.name,
+                is_fixed: node.is_fixed,
+                filing_type: node.filing_type,
+            }});
+            const blob = new Blob([JSON.stringify(exportData, '', '')], { type: 'application/text'});
+            const template = templateList.find(x => { return x.id === sourceTree.nodes[0].temp_id });
+            saveAs(blob, `${template.name}.json`);
+        }
+        importFromJSON(str) {
+            try {
+                const data = JSON.parse(str);
+                filingObj.batchUpdateFiling({ updateType: 'import', data });
+            } catch(err) {
+                toastr.error('导入文件格式错误,无法解析');
+            }
+        }
+        importFile(file) {
+            if (!file) return;
+            const self = this;
+            let reader = new FileReader();
+            reader.onload = function() {
+                self.importFromJSON(this.result);
+            };
+            reader.readAsText(file);
+        }
+    }
+    let multiObj = new MultiObj({ modal: 'multi-set', spread: 'multi-spread' });
+    $('#multi-setting').click(() => {
+        multiObj.reload(filingObj.dragTree);
+        multiObj.show();
+    });
+
+    $('#export-template').click(() => {
+        multiObj.exportFile(filingObj.dragTree);
+    });
+    $('#import-template').click(() => {
+        selectFile({
+            fileType: '*.json',
+            select: function(file) {
+                multiObj.importFile(file)
+            }
+        });
+    });
 });

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

@@ -200,7 +200,7 @@ const ledgerCheckUtil = {
                         checkZero(ZhCalc.sub(g.unit_price, child.unit_price));
                 });
                 if (!gcl) {
-                    gcl = { source: [], b_code: child.b_code || '', name: child.name || '', unit: child.unit || '', unit_price: child.unit_price || 0 }
+                    gcl = { source: [], b_code: child.b_code || '', name: child.name || '', unit: child.unit || '', unit_price: child.unit_price || 0 };
                     gatherGcl.push(gcl);
                 }
                 gcl.source.push(child);

+ 25 - 22
app/public/js/shenpi.js

@@ -340,7 +340,7 @@ $(document).ready(function () {
             let addhtml = '';
             if (flow.auditGroupList.length !== 0) {
                 for(const [i, auditGroup] of flow.auditGroupList.entries()) {
-                    addhtml += auditUtils.getAuditGroupHtml(this_code, auditGroup, i + 1);
+                    addhtml += this.getAuditGroupHtml(this_code, auditGroup, i + 1);
                 }
                 const addGroupHtml = this_code === 'change' && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
                     `<span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="${this_code}"><i class="fa fa-save"></i> 存为审批组</a></span>\n` : '';
@@ -348,7 +348,7 @@ $(document).ready(function () {
                     '                                            <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>\n' + addGroupHtml +
                     '                                        </li>';
             } else {
-                addhtml += auditUtils.getAuditGroupHtml(this_code, [], 1);
+                addhtml += this.getAuditGroupHtml(this_code, [], 1);
             }
             return addhtml;
         },
@@ -458,7 +458,8 @@ $(document).ready(function () {
                 _self.parents('.form-group').siblings('.lc-show').html('');
             } else if (this_status === sp_status.gdspl) {
                 const flow = sp_lc.find(x => { return x.code === this_code; });
-                flow.auditGroupList = data;
+                flow.auditGroupList = data.auditList;
+                flow.groupList = data.groupList;
                 let addhtml = auditUtils.getGroupHtml(flow, this_code);
                 addhtml += '<ul class="list-unstyled">\n';
                 addhtml += auditUtils.getgdsplHtml(flow, this_code);
@@ -472,7 +473,7 @@ $(document).ready(function () {
                     '                                                <span class="d-inline-block"></span>\n' +
                     '                                            </span>\n' +
                     '                                        </li>\n';
-                addhtml += data ? makeAudit(data) : makeSelectAudit(this_code);
+                addhtml += data.auditList ? makeAudit(data.auditList) : makeSelectAudit(this_code);
                 addhtml += '</ul>\n';
                 _self.parents('.form-group').siblings('.lc-show').html(addhtml);
             }
@@ -1135,21 +1136,21 @@ $(document).ready(function () {
         }
         console.log(prop);
         postData('/tender/' + cur_tenderid + '/shenpi/audit/save', prop, function (data) {
-            sp_lc[sp_type[code]].auditGroupList = data.group.auditGroupList || [];
+            flow.auditGroupList = data.group.auditGroupList || [];
             if (groupId) {
-                const index = sp_lc[sp_type[code]].groupList.findIndex(x => { return x.id === parseInt(groupId); });
-                sp_lc[sp_type[code]].groupList[index] = data.group;
+                const index = flow.groupList.findIndex(x => { return x.id === parseInt(groupId); });
+                flow.groupList[index] = data.group;
             } else {
-                if (!sp_lc[sp_type[code]].groupList) sp_lc[sp_type[code]].groupList = [];
-                for (const g of sp_lc[sp_type[code]].groupList) {
+                if (!flow.groupList) flow.groupList = [];
+                for (const g of flow.groupList) {
                     g.is_select = 0;
                 }
-                sp_lc[sp_type[code]].groupList.push(data.group);
+                flow.groupList.push(data.group);
             }
             // 配置页面
-            let addhtml = auditUtils.getGroupHtml(sp_lc[sp_type[code]], code);
+            let addhtml = auditUtils.getGroupHtml(flow, code);
             addhtml += '<ul class="list-unstyled">\n';
-            addhtml += auditUtils.getgdsplHtml(sp_lc[sp_type[code]], code);
+            addhtml += auditUtils.getgdsplHtml(flow, code);
             addhtml += '</ul>\n';
             $('.' + code + '_div').children('.lc-show').html(addhtml);
             $('#spzsave').modal('hide');
@@ -1159,6 +1160,7 @@ $(document).ready(function () {
     // 切换审批组
     $('body').on('change', '.group-list', function () {
         const this_code = $(this).parents('.lc-show').siblings('.form-group').find('input:checked').data('code');
+        const flow = sp_lc.find(x => { return x.code === this_code; });
         const groupId = parseInt($(this).val());
         const _self = $(this);
         const prop = {
@@ -1166,13 +1168,13 @@ $(document).ready(function () {
             groupId,
         }
         postData('/tender/' + cur_tenderid + '/shenpi/audit/save', prop, function (data) {
-            sp_lc[sp_type[this_code]].groupList.forEach(function (item) {
+            flow.groupList.forEach(function (item) {
                 item.is_select = 0;
             });
-            const group = sp_lc[sp_type[this_code]].groupList.find(x => { return x.id === groupId; });
+            const group = flow.groupList.find(x => { return x.id === groupId; });
             group.is_select = 1;
-            sp_lc[sp_type[this_code]].auditGroupList = group.auditGroupList;
-            const addhtml = auditUtils.getgdsplHtml(sp_lc[sp_type[this_code]], this_code);
+            flow.auditGroupList = group.auditGroupList;
+            const addhtml = auditUtils.getgdsplHtml(flow, this_code);
             _self.parents('.lc-show').children('ul').html(addhtml);
             _self.parents('.lc-show').find('.edit-spzsave').attr('data-group', groupId);
         });
@@ -1209,14 +1211,15 @@ $(document).ready(function () {
         }
         console.log(prop);
         postData('/tender/' + cur_tenderid + '/shenpi/audit/save', prop, function (data) {
-            const index = sp_lc[sp_type[code]].groupList.findIndex(x => { return x.id === group.id; });
-            sp_lc[sp_type[code]].groupList.splice(index, 1);
-            sp_lc[sp_type[code]].auditGroupList = sp_lc[sp_type[code]].groupList.length > 0 ? sp_lc[sp_type[code]].groupList[0].auditGroupList : [];
-            if (sp_lc[sp_type[code]].groupList.length > 0) sp_lc[sp_type[code]].groupList[0].is_select = 1;
+            const flow = findSplc(code);
+            const index = flow.groupList.findIndex(x => { return x.id === group.id; });
+            flow.groupList.splice(index, 1);
+            flow.auditGroupList = flow.groupList.length > 0 ? flow.groupList[0].auditGroupList : [];
+            if (flow.groupList.length > 0) flow.groupList[0].is_select = 1;
             // 配置页面
-            let addhtml = auditUtils.getGroupHtml(sp_lc[sp_type[code]], code);
+            let addhtml = auditUtils.getGroupHtml(flow, code);
             addhtml += '<ul class="list-unstyled">\n';
-            addhtml += auditUtils.getgdsplHtml(sp_lc[sp_type[code]], code);
+            addhtml += auditUtils.getgdsplHtml(flow, code);
             addhtml += '</ul>\n';
             $('.' + code + '_div').children('.lc-show').html(addhtml);
             $('#spzdelete').modal('hide');

+ 1 - 0
app/router.js

@@ -868,6 +868,7 @@ module.exports = app => {
     app.post('/sp/:id/file/rela', sessionAuth, subProjectCheck, 'fileController.relaFile');
     app.post('/sp/:id/file/rela/tender', sessionAuth, subProjectCheck, 'fileController.loadValidRelaTender');
     app.post('/sp/:id/file/rela/files', sessionAuth, subProjectCheck, 'fileController.loadRelaFiles');
+    app.post('/sp/:id/file/search', sessionAuth, subProjectCheck, 'fileController.search');
 
     // 支付审批
     app.get('/payment', sessionAuth, 'paymentController.index');

+ 10 - 0
app/service/file.js

@@ -134,6 +134,16 @@ module.exports = app => {
             await this.defaultUpdate(updateData);
             return updateData;
         }
+
+        async search(filing_type, keyword, limit = 1000) {
+            if (!filing_type || filing_type.length === 0 || !keyword) return [];
+            const sql = `SELECT * FROM ${this.tableName}` +
+                `  WHERE spid = ? and is_deleted = 0 and filing_type in (${filing_type.join(',')}) and filename like '%${keyword}%'`+
+                `  ORDER BY update_time DESC LIMIT 0, ${limit}`;
+            const result = await this.db.query(sql, [this.ctx.subProject.id]);
+            this.analysisFiles(result);
+            return result;
+        }
     }
 
     return Filing;

+ 47 - 12
app/service/filing.js

@@ -43,15 +43,34 @@ module.exports = app => {
         }
 
         analysisFilingType(filing) {
-            const curFilingType = filing.filter(f => {
-                return f.tree_level === 1;
-            });
-            curFilingType.sort((x, y) => {
-                return x.tree_order - y.tree_order;
-            });
-            return curFilingType.map(f => {
-                return { value: f.filing_type, name: f.name }
+            const copy = JSON.parse(JSON.stringify(filing));
+            const curFilingType = copy.filter(f => {
+                return f.is_fixed;
             });
+            const checkChildren = function (parent) {
+                parent.children = curFilingType.filter(x => { return x.tree_pid === parent.id; });
+                if (parent.children.length > 1) parent.children.sort((x, y) => { return x.tree_order - y.tree_order; });
+                for (const c of parent.children) {
+                    checkChildren(c);
+                }
+            };
+            const topFiling = curFilingType.filter(x => { return x.tree_level === 1; });
+            topFiling.sort((x, y) => { return x.tree_order - y.tree_order; });
+            for (const tp of topFiling) {
+                checkChildren(tp);
+            }
+            const result = [];
+            const getFilingType = function(arr, prefix = '') {
+                for (const a of arr) {
+                    if (a.children.length) {
+                        getFilingType(a.children, prefix ? prefix + '/' + a.name : a.name);
+                    } else {
+                        result.push({ value: a.filing_type, name: a.name, parentsName: prefix });
+                    }
+                }
+            };
+            getFilingType(topFiling);
+            return result;
         }
 
         async getFilingType(spid) {
@@ -73,7 +92,7 @@ module.exports = app => {
                 const parent = f.tree_pid !== rootId ? templateFiling.find(x => { return x.id === f.tree_pid; }) : null;
                 const newData = {
                     id: f.newId, tree_pid : parent ? parent.newId : rootId, tree_level: f.tree_level, tree_order: f.tree_order,
-                    spid, add_user_id: this.ctx.session.sessionUser.accountId, is_fixed: f.tree_level === 1,
+                    spid, add_user_id: this.ctx.session.sessionUser.accountId, is_fixed: f.is_fixed,
                     filing_type: f.filing_type, name: f.name,
                 };
                 insertData.push(newData);
@@ -85,11 +104,27 @@ module.exports = app => {
             }
         }
 
+        _filterValidFiling(filing, filingType) {
+            const validFiling = filing.filter(x => { return filingType.indexOf(x.filing_type) > -1;});
+            const checkParent = function(child) {
+                let parent = validFiling.find(x => { return x.id === child.tree_pid; });
+                if (!parent) {
+                    parent = filing.find(x => { return x.id === child.tree_pid; });
+                    validFiling.push(parent);
+                }
+                if (parent.tree_level > 1) checkParent(parent);
+            };
+            for (const vf of validFiling) {
+                if (vf.tree_level > 1 && vf.is_fixed) checkParent(vf);
+            }
+            return validFiling;
+        }
+
         async getValidFiling(spid, filingType) {
-            const condition = { spid, is_deleted: 0 };
-            if (filingType !== 'all') condition.filing_type = filingType;
             if (!filingType || filingType.length === 0) return [];
-            return await this.getAllDataByCondition({ where: condition });
+            const result = await this.getAllDataByCondition({ where: { spid, is_deleted: 0 } });
+            if (filingType === 'all') return result;
+            return this._filterValidFiling(result, filingType);
         }
 
         async getPosterityData(id){

+ 54 - 1
app/service/filing_template.js

@@ -106,8 +106,9 @@ module.exports = app => {
                 const insertData = {
                     id: this.uuid.v4(), temp_id: templateId, add_user_id: sessionUser.accountId,
                     tree_pid: parent ? parent.id : rootId, tree_level: parent ? parent.tree_level + 1 : 1, tree_order,
-                    name, filing_type,
+                    name, filing_type
                 };
+                insertData.is_fixed = insertData.tree_level === 1 ? 1 : (preChild ? preChild.is_fixed : 0);
                 const operate = await conn.insert(this.tableName, insertData);
                 if (operate.affectedRows === 0) throw '新增文件夹失败';
 
@@ -219,6 +220,58 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        async import(templateId, data) {
+            if (!data || data.length === 0) throw '导入数据不存在';
+
+            const insertData = [];
+            for (const d of data) {
+                if (d.id === undefined || d.tree_pid === undefined || d.tree_order === undefined || d.tree_level === undefined || d.name === undefined || d.is_fixed === undefined || d.filing_type === undefined) {
+                    throw '导入数据格式有误';
+                }
+                const parent = insertData.find(x => { return x.org_id === d.tree_pid });
+                insertData.push({
+                    id: this.uuid.v4(), temp_id: templateId, add_user_id: this.ctx.session.sessionUser.accountId,
+                    tree_pid: parent ? parent.id : rootId, tree_level: parent ? parent.tree_level + 1 : 1, tree_order: d.tree_order,
+                    name: d.name, filing_type: d.filing_type, is_fixed: parent ? d.is_fixed : 1, org_id: d.id,
+                });
+            }
+            insertData.forEach(x => { delete x.org_id; });
+
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.delete(this.tableName, { temp_id: templateId });
+                await conn.insert(this.tableName, insertData);
+                await conn.commit();
+            } catch(err) {
+                await conn.rollback();
+                throw '导入数据格式有误';
+            }
+            return await this.getData(templateId)
+        }
+
+        async multiUpdate(templateId, data) {
+            if (!data || data.length === 0) throw '提交数据格式错误';
+
+            const sourceData = await this.getData(templateId);
+
+            const validFields = ['id', 'is_fixed', 'name', 'filing_type', 'tree_order'];
+            const updateData = [];
+            for (const d of data) {
+                if (!d.id) throw '提交数据格式错误';
+                const sd = sourceData.find(x => { return x.id === d.id; });
+                if (!sd) throw '提交数据格式错误';
+
+                const nd = {};
+                for (const prop in d) {
+                    if (validFields.indexOf(prop) < 0) continue;
+                    nd[prop] = d[prop];
+                }
+                updateData.push(nd);
+            }
+            await this.db.updateRows(this.tableName, updateData);
+            return await this.getData(templateId);
+        }
     }
 
     return FilingTemplate;

+ 5 - 20
app/service/jpc_report.js

@@ -12,8 +12,10 @@ const rptDataExtractor = require('../reports/util/rpt_calculation_data_util');
 const RPT_DEF_PROPERTIES = require('../const/report_defined_properties');
 const needCustomTables = [
     'mem_custom_select',
-    'mem_gather_stage_bills', 'mem_gather_deal_bills', 'mem_gather_stage_pay', 'mem_gather_tender_info',
-    'mem_stage_sum_bills', 'mem_stage_sum_pay',
+    'mem_gather_stage_bills', 'mem_gather_deal_bills', 'mem_gather_stage_pay', 'mem_gather_tender_info', 'mem_gather_stage_pos',
+    'mem_gather_change', 'mem_gather_change_bills', 'mem_gather_stage_change', 'mem_gather_advance_pay',
+    'mem_gather_stage_jgcl', 'mem_gather_stage_yjcl', 'mem_gather_stage_bonus', 'mem_gather_stage_other', 'mem_gather_stage_safe_prod', 'mem_gather_stage_temp_land',
+    'mem_stage_sum_bills', 'mem_stage_sum_pos', 'mem_stage_sum_pay',
     'mem_jh_gather_im_change', 'mem_jh_im_change', 'mem_jh_gather_stage_bills_compare',
     'mem_material_sum_gl',
 ];
@@ -96,12 +98,9 @@ module.exports = app => {
             const rptDataUtil = new rptDataExtractor();
             const filterTables = [];
             const memFieldKeys = {};
-            let customSelect = {};
-            let customDefine = {};
             for (const rptTpl of rptTpls) {
                 rptDataUtil.initialize(rptTpl);
                 const filter = rptDataUtil.getDataRequestFilter();
-                // console.log(filter);
                 for (const table of filter.tables) {
                     if (filterTables.indexOf(table) < 0 && needCustomTables.indexOf(table) < 0) {
                         filterTables.push(table);
@@ -119,22 +118,8 @@ module.exports = app => {
                         }
                     }
                 }
-
-                // 输出报表的时候要把客户选择的数据的参数加进来
-                let finCustomSelect = {};
-                if (rptTpl[JV.NODE_CUSTOM_DEFINE]) {
-                    finCustomSelect = rptTpl[JV.NODE_CUSTOM_DEFINE] && rptTpl[JV.NODE_CUSTOM_DEFINE][JV.NODE_CUS_AUDIT_SELECT].enable
-                        ? await ctx.service.rptCustomDefine.getCustomDefine(params.tender_id, params.stage_id, rptTpl.id)
-                        : await ctx.service.rptCustomDefine.getCustomDefine(params.tender_id, -1, rptTpl.id);
-                }
-                if (finCustomSelect) {
-                    customDefine = rptTpl[JV.NODE_CUSTOM_DEFINE];
-                    customSelect = finCustomSelect;
-                }
-
             }
-            const rawDataObj = await ctx.service.report.getReportData(rptTpls[0].source_type, params, filterTables, memFieldKeys, customDefine, customSelect);
-            // const rawDataObj = await ctx.service.report.getReportData(rptTpls[0].source_type, params, filterTables, memFieldKeys, {}, {});
+            const rawDataObj = await ctx.service.report.getReportData(rptTpls[0].source_type, params, filterTables, memFieldKeys, {}, {});
             try {
                 const rptPageRstArray = [];
                 // 2. 一个一个模板创建数据

+ 15 - 2
app/service/ledger_audit.js

@@ -606,12 +606,25 @@ module.exports = app => {
          * @return {Promise<Array>} 查询结果集
          */
         async getAuditGroupByList(tender_id, times = 1, transaction = null) {
+            // const sql =
+            //     'SELECT la.`audit_id`, la.`status`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`tender_id`, la.`audit_order` ' +
+            //     '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+            //     '  WHERE la.`tender_id` = ? and la.`times` = ? GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
+            // const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tender_id, times];
+            // return transaction ? await transaction.query(sql, sqlParam) : await this.db.query(sql, sqlParam);
             const sql =
                 'SELECT la.`audit_id`, la.`status`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`tender_id`, la.`audit_order` ' +
                 '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
-                '  WHERE la.`tender_id` = ? and la.`times` = ? GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
+                '  WHERE la.`tender_id` = ? and la.`times` = ? ORDER BY la.`audit_order` DESC';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tender_id, times];
-            return transaction ? await transaction.query(sql, sqlParam) : await this.db.query(sql, sqlParam);
+            const result = transaction ? await transaction.query(sql, sqlParam) : await this.db.query(sql, sqlParam);
+            const audits = [];
+            for (const r of result) {
+                if (audits.findIndex(a => a.audit_id === r.audit_id) === -1) {
+                    audits.push(r);
+                }
+            }
+            return audits.reverse();
         }
 
         /**

+ 5 - 0
app/service/report.js

@@ -70,6 +70,10 @@ module.exports = app => {
             return result;
         }
 
+        clearReportCache() {
+            this.ctx.service.rptGatherMemory.clearCache();
+        }
+
         async tender(params, sourceFilters, memFieldKeys, customDefine, customSelect) {
             const [filters, specFilters] = this.getFilter(sourceFilters);
             const service = this.ctx.service;
@@ -620,6 +624,7 @@ module.exports = app => {
         }
 
         async getReportData(source_type, params, sourceFilters, memFieldKeys, customDefine, customSelect) {
+            this.clearReportCache();
             const sourceType = sourceTypeConst.sourceTypeData.find(x => { return x.id === source_type; });
             if (!sourceType && !this[sourceType.key]) return {};
 

+ 2 - 2
app/service/report_memory.js

@@ -1292,8 +1292,8 @@ module.exports = app => {
 
                 const data = await this.ctx.service.stageYjcl.getStageData(this.ctx.stage);
                 for (const d of data) {
-                    d.end_qty = this.ctx.helper.add(d.pre_qty, qty);
-                    d.end_tp = this.ctx.helper.add(d.pre_tp, tp);
+                    d.end_qty = this.ctx.helper.add(d.pre_qty, d.qty);
+                    d.end_tp = this.ctx.helper.add(d.pre_tp, d.tp);
                 }
                 return data;
             } catch (err) {

+ 13 - 3
app/service/rpt_gather_memory.js

@@ -269,11 +269,21 @@ module.exports = app => {
          */
         constructor(ctx) {
             super(ctx);
+            this.clearCache();
+        }
+
+        clearCache() {
             this.resultTree = null;
             this.resultPos = null;
             this.resultTenderInfo = [];
             this.resultDealPay = [];
             this.resultDealBills = [];
+            this.resultStageJgcl = [];
+            this.resultStageYjcl = [];
+            this.resultStageBonus = [];
+            this.resultStageOther = [];
+            this.resultStageSafeProd = [];
+            this.resultStageTempLand = [];
         }
 
         _checkFieldsExistReg(source, regStr) {
@@ -1594,10 +1604,10 @@ module.exports = app => {
         async _gatherStageYjcl(tender, stage) {
             const data = await this.ctx.service.stageYjcl.getStageData(stage);
             for (const d of data) {
-                d.end_qty = this.ctx.helper.add(d.pre_qty, qty);
-                d.end_tp = this.ctx.helper.add(d.pre_tp, tp);
+                d.end_qty = this.ctx.helper.add(d.pre_qty, d.qty);
+                d.end_tp = this.ctx.helper.add(d.pre_tp, d.tp);
             }
-            this.resultStageJgcl.push(...data);
+            this.resultStageYjcl.push(...data);
         }
         async _gatherMonthStageYjcl(sTender, month) {
             const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);

+ 3 - 2
app/service/shenpi_audit.js

@@ -26,7 +26,8 @@ module.exports = app => {
         }
 
         async getShenpi(tid, info) {
-            for (const sp of shenpiConst.sp_lc) {
+            const spConst = this._.cloneDeep(shenpiConst);
+            for (const sp of spConst.sp_lc) {
                 sp.status = info.shenpi ? info.shenpi[sp.code] : shenpiConst.sp_status.sqspr;
                 if (sp.status === shenpiConst.sp_status.gdspl) {
                     sp.groupList = await this.ctx.service.shenpiGroup.getGroupList(tid, sp.type) || [];
@@ -43,7 +44,7 @@ module.exports = app => {
                     sp.audit = await this.getAudit(tid, sp.type, sp.status);
                 }
             }
-            return shenpiConst;
+            return spConst;
         }
 
         async getAudit(tid, type, status) {

+ 1 - 0
app/service/stage.js

@@ -816,6 +816,7 @@ module.exports = app => {
                         }
                     }
                 }
+                await transaction.delete(this.ctx.service.stageYjcl.tableName, {sid: id});
                 await transaction.delete(this.ctx.service.stageBonus.tableName, { sid: id });
                 await transaction.delete(this.ctx.service.stageOther.tableName, { sid: id });
                 // 同步删除进度里所选的期

+ 2 - 2
app/service/stage_yjcl.js

@@ -62,7 +62,7 @@ module.exports = app => {
                 nd.m_order = d.m_order;
                 nd.spec = d.spec || '';
                 nd.unit = d.unit || '';
-                nd.tax = d.tax ? this.ctx.helper.round(d.tax, 0) : 0;
+                nd.tax = d.tax ? Math.min(Math.max(0, this.ctx.helper.round(d.tax, 0)), 100) : 0;
                 nd.arrive_time = d.arrive_time || '';
                 nd.source = d.source || '';
                 nd.bills_code = d.bills_code || '';
@@ -123,7 +123,7 @@ module.exports = app => {
                 if (d.m_order !== undefined) nd.m_order = d.m_order;
                 if (d.spec !== undefined) nd.spec = d.spec || '';
                 if (d.unit !== undefined) nd.unit = d.unit || '';
-                if (d.tax !== undefined) nd.tax = this.ctx.helper.round(d.tax, 0);
+                if (d.tax !== undefined) nd.tax = d.tax ? Math.min(Math.max(0, this.ctx.helper.round(d.tax, 0)), 100) : 0;
 
                 if (d.arrive_time !== undefined) nd.arrive_time = d.arrive_time || '';
                 if (d.source !== undefined) nd.source = d.source || '';

+ 2 - 2
app/service/sub_project.js

@@ -407,9 +407,9 @@ module.exports = app => {
             if (!template) throw '选择的文件类别不存在';
 
             const templateFiling = await this.ctx.service.filingTemplate.getAllDataByCondition({
-                where: { temp_id: template.id, tree_level: 1 },
+                where: { temp_id: template.id, is_fixed: 1 },
             });
-            const filing_type = templateFiling.map(x => { return x.filing_type; }).join(','), file_permission = '1,2';
+            const filing_type = this.ctx.service.filing.analysisFilingType(templateFiling).map(x => { return x.value; }).join(','), file_permission = '1,2';
             for (const u of users) {
                 const nm = orgMember.find(x => { return u.id === x.uid; });
                 if (nm) {

+ 1 - 0
app/service/tender_info.js

@@ -261,6 +261,7 @@ module.exports = app => {
                 });
                 for (const sb of stageBills) {
                     const b = bills.find(x => {return x.id === sb.lid});
+                    if (!b) continue;
                     const contract_tp = this.ctx.helper.mul(b.unit_price, sb.contract_qty, newDecimal.tp);
                     const qc_tp = this.ctx.helper.mul(b.unit_price, sb.qc_qty, newDecimal.tp);
                     if (contract_tp == sb.contract_tp && qc_tp === sb.qc_tp) continue;

+ 1 - 1
app/view/change/information.ejs

@@ -499,7 +499,7 @@
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
     let attData = JSON.parse(unescape('<%- escape(JSON.stringify(attList)) %>'));
     let currPageFileData = [];
-    const change_uid = parseInt('<%- change.uid %>');;
+    const change_uid = parseInt('<%- change.uid %>');
     const settleBills = JSON.parse(unescape('<%- escape(JSON.stringify(settleBills)) %>'));
     const settlePos = JSON.parse(unescape('<%- escape(JSON.stringify(settlePos)) %>'));
     const auditType = JSON.parse('<%- JSON.stringify(auditType) %>');

+ 1 - 1
app/view/change/relation.ejs

@@ -6,7 +6,7 @@
             <div>
                 <div class="d-inline-block">
                     <div class="btn-group btn-group-toggle group-tab">
-                        <a class="btn btn-sm btn-light" href="/tender/<%- ctx.tender.id %>/change/<%- ctx.change.cid %>/information">申请详情</a>
+                        <a class="btn btn-sm btn-light" href="/tender/<%- ctx.tender.id %>/change/<%- ctx.change.cid %>/information">变更详情</a>
                         <a class="btn btn-sm btn-light" href="/tender/<%- ctx.tender.id %>/change/<%- ctx.change.cid %>/report">输出报表</a>
                         <a class="btn btn-sm btn-light active" href="javascript:void(0);">关联数据</a>
                     </div>

+ 77 - 42
app/view/file/file.ejs

@@ -9,60 +9,95 @@
             </div>
         </div>
     </div>
-    <div class="content-wrap">
-        <div class="c-body">
-            <div class="sjs-height-0 row">
-                <div class="col-3 border-right pr-0">
-                    <div class="d-flex flex-row">
-                        <div class="btn-group">
-                            <button type="button" class="btn btn-sm  text-primary dropdown-toggle" data-toggle="dropdown" id="zhankai" aria-expanded="false">显示层级</button>
-                            <div class="dropdown-menu" aria-labelledby="zhankai" x-placement="bottom-start" style="position: absolute; transform: translate3d(0px, 21px, 0px); top: 0px; left: 0px; will-change: transform;">
-                                <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 class="content-wrap row pr-46">
+        <div class="row w-100 sub-content">
+            <div class="c-body" id="left-view" style="width: 100%">
+                <div class="sjs-height-0 row" style="width: 100%">
+                    <div class="col-3 border-right pr-0">
+                        <div class="d-flex flex-row">
+                            <div class="btn-group">
+                                <button type="button" class="btn btn-sm  text-primary dropdown-toggle" data-toggle="dropdown" id="zhankai" aria-expanded="false">显示层级</button>
+                                <div class="dropdown-menu" aria-labelledby="zhankai" x-placement="bottom-start" style="position: absolute; transform: translate3d(0px, 21px, 0px); top: 0px; left: 0px; will-change: transform;">
+                                    <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>
+                            <% if (canFiling) { %>
+                            <div class="p-2"><a href="javascript: void(0);" id="add-slibing">添加同级</a></div>
+                            <div class="p-2"><a href="javascript: void(0);" id="add-child">添加子级</a></div>
+                            <% } %>
                         </div>
-                        <% if (canFiling) { %>
-                        <div class="p-2"><a href="javascript: void(0);" id="add-slibing">添加同级</a></div>
-                        <div class="p-2"><a href="javascript: void(0);" id="add-child">添加子级</a></div>
-                        <% } %>
+                        <ul id="filing" class="ztree" style="overflow: auto"></ul>
                     </div>
-                    <ul id="filing" class="ztree" style="overflow: auto"></ul>
-                </div>
-                <div class="col-9" id="file-view" style="display: none">
-                    <div class="d-flex flex-row">
-                        <% if (canUpload) { %>
-                        <div class="py-2 pr-2"><a href="#add-file" data-toggle="modal" data-target="#add-file">上传文件</a></div>
-                        <div class="py-2 pr-2"><a href="#add-big-file" data-toggle="modal" data-target="#add-big-file">大文件上传</a></div>
-                        <div class="p-2" id="rela-file-btn"><a href="#rela-file" data-toggle="modal" data-target="#rela-file">导入文件</a></div>
-                        <div class="p-2"><a href="javascript: void(0)" id="batch-del-file-btn">批量删除</a></div>
-                        <% } %>
-                        <div class="p-2"><a href="javascript: void(0)" id="batch-download">批量下载</a></div>
-                        <div class="p-2">
+                    <div class="col-9 pr-0" id="file-view" style="display: none">
+                        <div class="d-flex flex-row">
+                            <% if (canUpload) { %>
+                            <div class="py-2 pr-2"><a href="#add-file" data-toggle="modal" data-target="#add-file">上传文件</a></div>
+                            <div class="py-2 pr-2"><a href="#add-big-file" data-toggle="modal" data-target="#add-big-file">大文件上传</a></div>
+                            <div class="p-2" id="rela-file-btn"><a href="#rela-file" data-toggle="modal" data-target="#rela-file">导入文件</a></div>
+                            <div class="p-2"><a href="javascript: void(0)" id="batch-del-file-btn">批量删除</a></div>
+                            <% } %>
+                            <div class="p-2"><a href="javascript: void(0)" id="batch-download">批量下载</a></div>
+                            <div class="p-2">
                             <span id="showPage">
                                 <a href="javascript:void(0);" class="page-select ml-3" content="pre"><i class="fa fa-chevron-left"></i></a>
                                 <span id="curPage">1</span>/<span id="curTotalPage">10</span>
                                 <a href="javascript:void(0);" class="page-select mr-3" content="next"><i class="fa fa-chevron-right"></i></a>
                             </span>
+                            </div>
                         </div>
+                        <table class="table table-bordered">
+                            <thead>
+                            <tr class="text-center">
+                                <th width="60px">选择</th>
+                                <th>文件名称 <span name="file-sort" field="filename" tag="filename|desc"><i class="fa fa-sort" aria-hidden="true"></i></span></th>
+                                <th width="10%">上传人</th>
+                                <th width="20%">上传时间 <span name="file-sort" field="create_time" tag="create_time|asc"><i class="fa fa-sort-amount-desc" aria-hidden="true"></i></span></th>
+                                <th width="10%">文件类型</th>
+                            </tr>
+                            </thead>
+                            <tbody id="file-list">
+                            </tbody>
+                        </table>
                     </div>
-                    <table class="table table-bordered">
-                        <thead>
-                        <tr class="text-center">
-                            <th width="60px">选择</th>
-                            <th>文件名称 <span name="file-sort" field="filename" tag="filename|desc"><i class="fa fa-sort" aria-hidden="true"></i></span></th>
-                            <th width="10%">上传人</th>
-                            <th width="20%">上传时间 <span name="file-sort" field="create_time" tag="create_time|asc"><i class="fa fa-sort-amount-desc" aria-hidden="true"></i></span></th>
-                            <th width="10%">文件类型</th>
-                        </tr>
-                        </thead>
-                        <tbody id="file-list">
-                        </tbody>
-                    </table>
                 </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 mr-1">
+                        <div class="sjs-bar mt-1">
+                            <div class="input-group input-group-sm pb-1">
+                                <div class="input-group-prepend">
+                                    <select class="input-group-text" id="search-filter">
+                                        <option value="cur">当前节点</option>
+                                        <option value="all">全部节点</option>
+                                    </select>
+                                </div>
+                                <input id="search-keyword" type="text" class="form-control" autocomplete="off" placeholder="输入文件名称查找" aria-label="Recipient\'s username" aria-describedby="button-addon2">
+                                <div class="input-group-append"><button class="btn btn-outline-secondary" type="button">搜索</button></div>
+                            </div>
+                        </div>
+                        <div class="sjs-sh scroll-y">
+                            <table class="table table-bordered">
+                                <tr class="text-center"><th>文件名称</th><th width="15%">上传人</th><th width="15%">操作</th></tr>
+                                <tbody id="search-list"></tbody>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!--右侧菜单-->
+        <div class="side-menu">
+            <ul class="nav flex-column right-nav" id="side-menu">
+                <li>
+                    <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
+                </li>
+            </ul>
         </div>
     </div>
 </div>

+ 2 - 2
app/view/file/file_modal.ejs

@@ -11,7 +11,7 @@
                         <div class="d-flex justify-content-center bg-graye">
                             <div class="p-2 vertical-middle"><input class="mr-1" type="checkbox" id="filing-select-all">文档类别</div>
                         </div>
-                        <div class="modal-height-400">
+                        <div class="modal-height-400 scroll-y">
                             <div class="category">
                                 <ul style="list-style: none">
                                     <% for (const ft of filingTypes) { %>
@@ -19,7 +19,7 @@
                                         <div class="form-check">
                                             <input class="form-check-input" name="cbft" type="checkbox" value="<%- ft.value %>" id="ftCheck<%- ft.value %>">
                                             <label class="form-check-label" for="ftCheck<%- ft.value %>"></label>
-                                            <span name="ftName" ftid="<%- ft.value %>"><%- ft.name %></span>
+                                            <span name="ftName" ftid="<%- ft.value %>" title="<%- ft.parentsName %>"><%- ft.name %></span>
                                         </div>
                                     </li>
                                     <% } %>

+ 5 - 0
app/view/file/template.ejs

@@ -44,6 +44,11 @@
                             <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
                             <button class="btn btn-sm btn-primary"><i class="fa fa-refresh" aria-hidden="true"></i> 初始化模板</button>
                         </form>
+                        <div class="ml-auto p-2">
+                            <a href="javascript: void(0);" class="btn btn-sm btn-primary" id="import-template">导入</a>
+                            <a href="javascript: void(0);" class="btn btn-sm btn-primary" id="export-template">导出</a>
+                            <a href="javascript: void(0);" class="btn btn-sm btn-primary" id="multi-setting">附加配置</a>
+                        </div>
                     </div>
                     <div>
                         <ul id="filing" class="ztree" style="overflow: auto"></ul>

+ 21 - 0
app/view/file/template_modal.ejs

@@ -0,0 +1,21 @@
+<% include ../shares/select_file_modal.ejs %>
+<div class="modal fade" id="multi-set" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="tender-select-title">更多配置</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="modal-height-300" id="multi-spread">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button class="btn btn-sm btn-primary" id="multi-set-ok">确定</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 1 - 1
app/view/ledger/explode.ejs

@@ -53,7 +53,7 @@
                 <% if (tender.ledger_status === auditConst.status.checkNo) { %>
                     <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-dark sp-list-btn">审批退回</a>
                 <% } else if (tender.ledger_status === auditConst.status.checking && curAuditor.audit_id !== ctx.session.sessionUser.accountId) { %>
-                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-dark sp-list-btn">审批中</a>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-dark">审批中</a>
                 <% } else if (tender.ledger_status === auditConst.status.checked) { %>
                     <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm sp-list-btn">审批完成</a>
                 <% } %>

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

@@ -472,7 +472,8 @@
 </div>
 <% } %>
 
-<% if (ctx.session.sessionUser.accountId === ctx.tender.data.user_id && (ctx.tender.data.ledger_status === auditConst.status.uncheck || ctx.tender.data.ledger_status === auditConst.status.checkNo)) { %>
+<% console.log(ctx.session.sessionUser.accountId, ctx.tender.data.user_id, ctx.session.sessionUser.is_admin) %>
+<% if ((ctx.session.sessionUser.accountId === ctx.tender.data.user_id || ctx.session.sessionUser.is_admin) && (ctx.tender.data.ledger_status === auditConst.status.uncheck || ctx.tender.data.ledger_status === auditConst.status.checkNo)) { %>
     <script>
         const shenpi_status = <%- ctx.tender.info.shenpi.ledger %>;
         const shenpiConst =  JSON.parse('<%- JSON.stringify(shenpiConst) %>');

+ 40 - 0
app/view/shares/select_file_modal.ejs

@@ -0,0 +1,40 @@
+<div class="modal fade" id="select-file" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择文件</h5>
+            </div>
+            <div class="modal-body">
+                <p><span id="sf-hint">请选择.xls和.xlsx 文件</span><a class="ml-2" id="sf-template">示例</a></p></p>
+                <div class="form-group">
+                    <label for="sf-file">选择文件</label><i class="fa fa-spinner fa-pulse fa-lg fa-fw text-primary" id="select-excel-loading" style="display: none;"></i>
+                    <input type="file" class="form-control-file" id="sf-file" accept="*.json" name="file">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="sf-ok">确认</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const selectFile = function(setting) {
+        if (!setting || !setting.fileType || !setting.select) return;
+
+        if (setting.template) {
+            $('#sf-template').attr('href', setting.template).show();
+        } else {
+            $('#sf-template').hide();
+        }
+        $('#sf-hint').text(setting.hint || `请选择${setting.fileType}文件`);
+        $('#sf-file').val('');
+        $('#sf-ok').off('click');
+        $('#sf-file').attr('accept', setting.fileType);
+        $('#select-file').modal('show');
+        $('#sf-ok').on("click", function () {
+            setting.select(document.getElementById('sf-file').files[0]);
+            $('#select-file').modal('hide');
+        });
+    };
+</script>

+ 0 - 1
app/view/tender/shenpi.ejs

@@ -32,7 +32,6 @@
                                             <% } %>
                                         </div>
                                     </div>
-                                    <% if (!shenpi.sp_status_list[sp.status]) console.log(sp) %>
                                     <div class="alert alert-warning"><%- shenpi.sp_status_list[sp.status].name %>:<%- shenpi.sp_status_list[sp.status].msg %></div>
                                     <div class="lc-show">
                                     <% if (sp.status === shenpi.sp_status.gdspl) { %>

+ 8 - 1
config/web.js

@@ -1204,6 +1204,7 @@ const JsFiles = {
                     '/public/js/shares/aliyun-oss-sdk.min.js',
                 ],
                 mergeFiles: [
+                    '/public/js/div_resizer.js',
                     '/public/js/shares/ali_oss.js',
                     '/public/js/shares/drag_tree.js',
                     '/public/js/path_tree.js',
@@ -1213,8 +1214,14 @@ const JsFiles = {
                 mergeFile: 'file_detail',
             },
             template: {
-                files: ['/public/js/ztree/jquery.ztree.core.js', '/public/js/ztree/jquery.ztree.exedit.js',],
+                files: [
+                    '/public/js/ztree/jquery.ztree.core.js', '/public/js/ztree/jquery.ztree.exedit.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/file-saver/FileSaver.js',
+                ],
                 mergeFiles: [
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/path_tree.js',
                     '/public/js/shares/drag_tree.js',
                     '/public/js/filing_template.js',
                 ],

+ 10 - 4
sql/update.sql

@@ -403,10 +403,7 @@ ADD COLUMN `features` varchar(1000) NOT NULL DEFAULT '' COMMENT '项目特征' A
 ALTER TABLE `zh_revise_bills_99`
 ADD COLUMN `features` varchar(1000) NOT NULL DEFAULT '' COMMENT '项目特征' AFTER `memo`;
 
-ALTER TABLE zh_change`
-ADD COLUMN `order_site` int(11) NULL DEFAULT NULL COMMENT 'order_by为自定义时插入到清单id值' AFTER `order_by`;
-
-ALTER TABLE `zh_rpt_tree_node_cust` 
+ALTER TABLE `zh_rpt_tree_node_cust`
 ADD COLUMN `tender_id` INT NULL DEFAULT -1 COMMENT '新需求,跟标段走,不跟客户走' AFTER `cust_acc_id`,
 ADD INDEX `tender` (`tender_id` ASC);
 ;
@@ -443,3 +440,12 @@ CREATE TABLE `zh_stage_yjcl`  (
   `shistory` text NULL COMMENT '审批历史',
   PRIMARY KEY (`id`)
 );
+
+ALTER TABLE `zh_budget_std`
+ADD COLUMN `ht_project_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '合同-项目合同模版-id列表(‘,’分隔)' AFTER `zb_bills_id`,
+ADD COLUMN `ht_tender_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '合同-标段合同模版-id列表(‘,’分隔)' AFTER `ht_project_template_id`;
+
+
+-- update请放在最后
+
+Update zh_filing_template SET is_fixed = 1 WHERE tree_level = 1;