Kaynağa Gözat

成本管理,审批流程配置

MaiXinRong 2 ay önce
ebeveyn
işleme
74ec4f465d

+ 2 - 2
app/const/audit.js

@@ -11,8 +11,8 @@
 const auditType = (function () {
     const types = [
         { key: 'common', name: '个人', value: 1, short: '', long: '', class: '', },
-        { key: 'and', name: '会签', value: 2, short: '会', long: '多人会签', class: 'primary', valid: ['ledger', 'revise', 'stage', 'change', 'material', 'financial', 'phasePay'], setValid: ['stage'] },
-        { key: 'or', name: '或签', value: 3, short: '或', long: '多人或签', class: 'success', valid: ['ledger', 'revise', 'stage', 'change', 'material', 'financial', 'phasePay'], setValid: ['stage'] },
+        { key: 'and', name: '会签', value: 2, short: '会', long: '多人会签', class: 'primary', valid: ['ledger', 'revise', 'stage', 'change', 'material', 'financial', 'phasePay', 'cost_stage_ledger', 'cost_stage_book', 'cost_stage_analysis'], setValid: ['stage'] },
+        { key: 'or', name: '或签', value: 3, short: '或', long: '多人或签', class: 'success', valid: ['ledger', 'revise', 'stage', 'change', 'material', 'financial', 'phasePay', 'cost_stage_ledger', 'cost_stage_book', 'cost_stage_analysis'], setValid: ['stage'] },
         { key: 'union', name: '协同', value: 4, short: '协', long: '多人协同', class: 'warning', valid: ['stage']},
         { key: 'multi', name: '分组审批', value: 5, short: '组', long: '分组审批', class: 'danger', valid: ['stage'] },
     ];

+ 16 - 0
app/const/shenpi.js

@@ -21,10 +21,19 @@ const sp_type = {
     inspection: 10,
     safe_inspection: 11,
     safe_payment: 12,
+    // 成本报审相关审批流程,占用13-15,请勿重复使用
+    // cost_stage_ledger: 13,
+    // cost_stage_book: 14,
+    // cost_stage_analysis: 15,
 };
 const sp_other_type = {
     financial: 8,
 };
+const cost_sp_type = {
+    cost_stage_ledger: 13,
+    cost_stage_book: 14,
+    cost_stage_analysis: 15,
+};
 // const sp_name = [];
 // sp_name[sp_type.advance] = '预付款审批';
 // sp_name[sp_type.ledger] = '台账审批';
@@ -47,6 +56,11 @@ const sp_lc = [
     { code: 'safe_payment', type: sp_type.safe_payment, name: '安全计量审批' },
     { code: 'safe_inspection', type: sp_type.safe_inspection, name: '安全巡检审批' },
 ];
+const cost_sp_lc = [
+    { code: 'cost_stage_ledger', type: cost_sp_type.cost_stage_ledger, name: '成本报审' },
+    { code: 'cost_stage_book', type: cost_sp_type.cost_stage_book, name: '财务账面' },
+    { code: 'cost_stage_analysis', type: cost_sp_type.cost_stage_analysis, name: '成本分析' },
+];
 
 const sp_status = {
     sqspr: 1, // 授权审批人
@@ -74,7 +88,9 @@ const change_type_list = [
 module.exports = {
     sp_type,
     sp_other_type,
+    cost_sp_type,
     sp_lc,
+    cost_sp_lc,
     sp_status,
     sp_status_list,
     change_type,

+ 3 - 0
app/const/tender_info.js

@@ -197,6 +197,9 @@ const defaultInfo = {
         inspection: 1,
         safe_payment: 1,
         safe_inspection: 1,
+        cost_stage_ledger: 1,
+        cost_stage_book: 1,
+        cost_stage_analysis: 1,
     },
     ledger_check: {
         same_code: true,

+ 1 - 1
app/controller/cost_controller.js

@@ -163,7 +163,7 @@ module.exports = app => {
                 if (!stage_date) throw '请选择年月';
 
                 const stages = await ctx.service.costStage.getAllStages(ctx.tender.id, stage_type, 'DESC');
-                const unCompleteCount = stages.filter(s => { return s.status !== auditConst.common.status.checked; }).length;
+                const unCompleteCount = stages.filter(s => { return s.status !== audit.common.status.checked; }).length;
                 if (unCompleteCount.length > 0) throw '最新一期未审批通过,请审批通过后再新增';
 
                 const newStage = await ctx.service.costStage.add(ctx.tender.id, stage_type, stage_date);

+ 76 - 53
app/controller/tender_controller.js

@@ -998,65 +998,88 @@ module.exports = app => {
             ctx.body = responseData;
         }
 
+        _getSpecShenpiUrl(ctx) {
+            if (ctx.url === `/sp/${ctx.subProject.id}/cost/tender/${ctx.tender.id}/flow`) return 'cost';
+        }
         async shenpiSet(ctx) {
-            if (ctx.session.sessionUser.is_admin === 0) {
-                ctx.request.headers.referer ? ctx.redirect(ctx.request.headers.referer) : ctx.redirect(`/sp/${ctx.subProject.id}/list`);
-            }
-            // 获取所有项目参与者
-            const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
-            const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
-            const accountGroupList = unitList.map(item => {
-                const groupList = accountList.filter(item1 => item1.company === item.name);
-                return { groupName: item.name, groupList };
-            }).filter(x => { return x.groupList.length > 0; });
-            // 获取固定审批流 or 固定终审
-            const spConst = ctx.helper._.cloneDeep(shenpiConst);
-            for (const sp of spConst.sp_lc) {
-                sp.status = ctx.tender.info.shenpi ? ctx.tender.info.shenpi[sp.code] : spConst.sp_status.sqspr;
-                if (sp.status === shenpiConst.sp_status.gdspl) {
-                    sp.groupList = await ctx.service.shenpiGroup.getGroupList(ctx.tender.id, sp.type) || [];
-                    if (sp.groupList && sp.groupList.length > 0) {
-                        for (const group of sp.groupList) {
-                            if (group.change_type) group.change_type = JSON.parse(group.change_type);
-                            group.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.tender.id, sp.type, sp.status, group.id);
-                            if (group.is_select) sp.auditGroupList = group.auditGroupList;
+            try {
+                const specShenpiType = this._getSpecShenpiUrl(ctx);
+                // 获取所有项目参与者
+                const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
+                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
+                const accountGroupList = unitList.map(item => {
+                    const groupList = accountList.filter(item1 => item1.company === item.name);
+                    return { groupName: item.name, groupList };
+                }).filter(x => { return x.groupList.length > 0; });
+                /**
+                 * 如果是期审批流程配置,但是不跟期内审批流程使用同一页面,需:
+                 * 1. 在 this._getSpecShenpiUrl 内增加判断审批页面的类型,例如cost
+                 * 2. 在 const/shenpi.js 内定义对应的cost_sp_lc和cost_sp_type,
+                 * 3. 在 validSubMenu 内定义右侧菜单的ejs文件位置,文件位置基于view/tender路径,需提供相对路径
+                 */
+                const validSubMenu = {
+                    cost: { common: '../cost/list_menu.ejs', mini: '../cost/list_mini_menu.ejs'},
+                    default: { common: './tender_sub_menu.ejs', mini: './tender_sub_mini_menu.ejs'}
+                };
+                const subMenu = specShenpiType ? validSubMenu[specShenpiType] : validSubMenu.default;
+                // 获取固定审批流 or 固定终审
+                const spConst = ctx.helper._.cloneDeep(shenpiConst);
+                if (specShenpiType) {
+                    spConst.sp_lc = spConst[specShenpiType + '_sp_lc'];
+                    spConst.sp_type = spConst[specShenpiType + '_sp_type'];
+                }
+                for (const sp of spConst.sp_lc) {
+                    sp.status = ctx.tender.info.shenpi ? ctx.tender.info.shenpi[sp.code] : spConst.sp_status.sqspr;
+                    if (sp.status === shenpiConst.sp_status.gdspl) {
+                        sp.groupList = await ctx.service.shenpiGroup.getGroupList(ctx.tender.id, sp.type) || [];
+                        if (sp.groupList && sp.groupList.length > 0) {
+                            for (const group of sp.groupList) {
+                                if (group.change_type) group.change_type = JSON.parse(group.change_type);
+                                group.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.tender.id, sp.type, sp.status, group.id);
+                                if (group.is_select) sp.auditGroupList = group.auditGroupList;
+                            }
+                        } else {
+                            sp.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.tender.id, sp.type, sp.status);
                         }
-                    } else {
-                        sp.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.tender.id, sp.type, sp.status);
+                    } else if (sp.status === shenpiConst.sp_status.gdzs) {
+                        sp.audit = await ctx.service.shenpiAudit.getAudit(ctx.tender.id, sp.type, sp.status);
                     }
-                } else if (sp.status === shenpiConst.sp_status.gdzs) {
-                    sp.audit = await ctx.service.shenpiAudit.getAudit(ctx.tender.id, sp.type, sp.status);
                 }
-            }
-            const tenders = await ctx.service.tender.getList('', null, 1);
-            const removeTenders = await ctx.service.shenpiAudit.getRemoveTenders(tenders);
-            if (removeTenders.length > 0) {
-                ctx.helper._.remove(tenders, function(n) {
-                    return removeTenders.indexOf(n.id) !== -1;
-                });
-            }
-            const categoryData = await ctx.service.category.getAllCategory(ctx.subProject);
-            // 是否修订中
-            const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
-            const revising = (lastRevise && lastRevise.status !== auditConst.revise.status.checked) || false;
+                const tenders = await ctx.service.tender.getList('', null, 1);
+                const removeTenders = await ctx.service.shenpiAudit.getRemoveTenders(tenders);
+                if (removeTenders.length > 0) {
+                    ctx.helper._.remove(tenders, function(n) {
+                        return removeTenders.indexOf(n.id) !== -1;
+                    });
+                }
+                const categoryData = await ctx.service.category.getAllCategory(ctx.subProject);
+                // 是否修订中
+                const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                const revising = (lastRevise && lastRevise.status !== auditConst.revise.status.checked) || false;
 
-            const cooperationNum = await ctx.service.ledgerCooperation.count({ tid: ctx.tender.id, status: 1 });
+                const cooperationNum = await ctx.service.ledgerCooperation.count({ tid: ctx.tender.id, status: 1 });
+
+                const renderData = {
+                    subMenu,
+                    shenpi: spConst,
+                    accountList,
+                    accountGroup: accountGroupList,
+                    tenders,
+                    categoryData,
+                    auditConst,
+                    revising,
+                    measureType,
+                    cooperationNum,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.tender.shenpi),
+                    auditType: auditConst.auditType,
+                    change_type_list: shenpiConst.change_type_list,
+                };
+                await this.layout('tender/shenpi.ejs', renderData, 'tender/shenpi_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.redirect(ctx.request.header.referer || `/sp/${ctx.subProject.id}/list`);
+            }
 
-            const renderData = {
-                shenpi: spConst,
-                accountList,
-                accountGroup: accountGroupList,
-                tenders,
-                categoryData,
-                auditConst,
-                revising,
-                measureType,
-                cooperationNum,
-                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.tender.shenpi),
-                auditType: auditConst.auditType,
-                change_type_list: shenpiConst.change_type_list,
-            };
-            await this.layout('tender/shenpi.ejs', renderData, 'tender/shenpi_modal.ejs');
         }
 
         async saveTenderInfoShenpi(ctx) {

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

@@ -686,7 +686,7 @@ $(document).ready(function() {
                     if (node) updateData.del.push(node.id);
                 }
                 if (updateData.del.length === 0) return;
-                
+
                 deleteAfterHint(function () {
                     postData(window.location.pathname + '/update', updateData, function (result) {
                         billsTag.afterDeletePos(result.detail.del.map(x => { return detailObj.data.getItem(x); }));
@@ -1015,6 +1015,31 @@ $(document).ready(function() {
             };
             return {importByType, importBySelect};
         })();
+        $.contextMenu.types.batchInsert = function (item, opt, root) {
+            const self = this;
+            if ($.isFunction(item.icon)) {
+                item._icon = item.icon.call(this, this, $t, key, item);
+            } else {
+                if (typeof(item.icon) === 'string' && item.icon.substring(0, 3) === 'fa-') {
+                    // to enable font awesome
+                    item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon;
+                } else {
+                    item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon;
+                }
+            }
+            this.addClass(item._icon);
+            const $obj = $('<div>' + item.name + '<input class="text-right ml-1 mr-1" type="tel" max="100" min="1" value="' + item.value + '" style="width: 30px; height: 18px; padding-right: 4px;">行</div>')
+                .appendTo(this);
+            const $input = $obj.find('input');
+            const event = () => {
+                if (self.hasClass('context-menu-disabled')) return;
+                item.batchInsert($input[0], root);
+            };
+            $obj.on('click', event).keypress(function (e) {if (e.keyCode === 13) { event(); }});
+            $input.click((e) => {e.stopPropagation();})
+                .keyup((e) => {if (e.keyCode === 13) item.batchInsert($input[0], root);})
+                .on('input', function () {this.value = this.value.replace(/[^\d]/g, '');});
+        };
         billsContextMenuOptions.items.create = {
             name: '新增',
             icon: 'fa-sign-in',
@@ -1068,6 +1093,38 @@ $(document).ready(function() {
                 return !(valid && first && sameParent && !nodeUsed);
             }
         };
+        billsContextMenuOptions.items.batchInsert = {
+            name: '批量插入',
+            type: 'batchInsert',
+            value: '2',
+            icon: 'fa-sign-in',
+            batchInsert: function (obj, root) {
+                if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
+                    obj.value = obj.max;
+                    toastr.warning('批量插入不可多于' + obj.max);
+                } else if(_.toNumber(obj.value) < _.toNumber(obj.min)) {
+                    obj.value = obj.min;
+                    toastr.warning('批量插入不可少于' + obj.min);
+                } else {
+                    billsObj.baseOpr('add', parseInt(obj.value));
+                    root.$menu.trigger('contextmenu:hide');
+                }
+            },
+            disabled: function (key, opt) {
+                const sheet = billsObj.sheet;
+                const selection = sheet.getSelections();
+                const sel = selection ? selection[0] : sheet.getSelections()[0];
+                const row = sel ? sel.row : -1;
+                const tree = sheet.zh_tree;
+                if (!tree) return true;
+                const first = sheet.zh_tree.nodes[row];
+                const valid = !sheet.zh_setting.readOnly;
+                return !(valid && first);
+            },
+            visible: function (key, opt) {
+                return !readOnly;
+            }
+        };
         billsContextMenuOptions.items.baseSpr = '----';
         billsContextMenuOptions.items.importDealByTree = {
             name: '导入合同数据(自选)',

+ 2 - 1
app/router.js

@@ -548,6 +548,7 @@ module.exports = app => {
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/file/upload', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.uploadStageFile');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/file/delete', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.deleteStageFile');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/tag', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.tag');
+    app.get('/sp/:id/cost/tender/:tid/flow', sessionAuth, subProjectCheck, tenderCheck, projectManagerCheck, 'tenderController.shenpiSet');
 
     // 安全管理
     // 安全计量
@@ -597,7 +598,7 @@ module.exports = app => {
     app.post('/tender/:id/build', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.saveBuildStatus');
     app.post('/tender/rule', sessionAuth, 'tenderController.rule');
     app.post('/tender/:id/rule/first', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.ruleFirst');
-    app.get('/tender/:id/shenpi', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.shenpiSet');
+    app.get('/tender/:id/shenpi', sessionAuth, tenderCheck, subProjectCheck, projectManagerCheck, 'tenderController.shenpiSet');
     app.post('/tender/:id/shenpi/save', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.saveTenderInfoShenpi');
     app.post('/tender/:id/shenpi/audit/save', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.saveShenpiAudit');
     app.post('/tender/:id/shenpi/ass/load', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.loadAuditAss');

+ 5 - 2
app/view/cost/list_menu_list.ejs

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

+ 2 - 2
app/view/tender/shenpi.ejs

@@ -1,8 +1,8 @@
-<% include ./tender_sub_menu.ejs %>
+<%- include(subMenu.common) %>
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex">
-            <% include ./tender_sub_mini_menu.ejs %>
+            <%- include(subMenu.mini) %>
             <h2>审批流程设置</h2>
         </div>
     </div>

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

@@ -88,6 +88,7 @@
     </div>
 </div>
 <!--多人协同-->
+<% if (shenpi.sp_type.stage) { %>
 <div class="modal fade" id="cooperation" data-backdrop="static">
     <div class="modal-dialog modal-xl" role="document">
         <div class="modal-content">
@@ -166,6 +167,7 @@
         </div>
     </div>
 </div>
+<% } %>
 <div class="modal fade" id="union" data-backdrop="static">
     <div class="modal-dialog modal-xl" role="document">
         <div class="modal-content">

+ 1 - 0
config/web.js

@@ -143,6 +143,7 @@ const JsFiles = {
                 files: [
                     '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
                     '/public/js/decimal.min.js',
+                    '/public/js/component/menu.js',
                 ],
                 mergeFiles: [
                     '/public/js/sub_menu.js',