Przeglądaj źródła

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

MaiXinRong 14 godzin temu
rodzic
commit
772b32a8e5

+ 22 - 4
app/const/contract.js

@@ -26,8 +26,8 @@ const typeName = {
 const colSet = {
     [type.expenses]: [
         { name: '合同类型', field: 'type', fixed: ['alias'], gd: true },
-        { name: '甲方', field: 'party_a', fixed: ['alias'], gd: true },
-        { name: '乙方', field: 'party_b', fixed: ['alias'], gd: true },
+        { name: '甲方', field: 'party_a', fixed: ['alias'], gd: true, removable: true },
+        { name: '乙方', field: 'party_b', fixed: ['alias'], gd: true, removable: true },
         { name: '结算金额', field: 'settle_price', fixed: ['alias'], gd: true },
         { name: '累计应付', field: 'yf_price', fixed: ['alias'] },
         { name: '应付进度', field: 'stackedBar', fixed: ['alias'] },
@@ -40,8 +40,8 @@ const colSet = {
     ],
     [type.income]: [
         { name: '合同类型', field: 'type', fixed: ['alias'], gd: true },
-        { name: '甲方', field: 'party_a', fixed: ['alias'], gd: true },
-        { name: '乙方', field: 'party_b', fixed: ['alias'], gd: true },
+        { name: '甲方', field: 'party_a', fixed: ['alias'], gd: true, removable: true },
+        { name: '乙方', field: 'party_b', fixed: ['alias'], gd: true, removable: true },
         { name: '结算金额', field: 'settle_price', fixed: ['alias'], gd: true },
         { name: '累计应回', field: 'yf_price', fixed: ['alias'] },
         { name: '应回进度', field: 'stackedBar', fixed: ['alias'] },
@@ -112,6 +112,23 @@ const defaultAttribute = {
     pay_condition: '',
 };
 
+// 可添加至合同管理表格的合同详情字段。已在基础表格中的合同编号、合同名称、
+// 合同金额、备注,以及创建时间和两个签约人不在可选范围内。
+const detailColumnSet = [
+    { name: '税率(%)', field: 'tax', fixed: ['alias'], type: 'number', removable: true },
+    { name: '原合同金额', field: 'price', fixed: ['alias'], type: 'number', removable: true },
+    { name: '补充合同金额', field: 'supplement_price', fixed: ['alias'], type: 'number', removable: true },
+    { name: '累计付款/回款', field: 'pay_price', fixed: ['alias'], type: 'number', removable: true },
+    { name: '累计扣款', field: 'debit_price', fixed: ['alias'], type: 'number', removable: true },
+    { name: '待支付/待回款', field: 'df_price', fixed: ['alias'], type: 'number', removable: true },
+    { name: '签订日期', field: 'sign_date', fixed: ['alias'], type: 'date', removable: true },
+    { name: '签约地点', field: 'address', fixed: ['alias'], type: 'text', removable: true },
+    { name: '收款单位', field: 'entity', fixed: ['alias'], type: 'text', removable: true, contractTypes: [type.expenses] },
+    { name: '收款开户行', field: 'bank', fixed: ['alias'], type: 'text', removable: true, contractTypes: [type.expenses] },
+    { name: '收款账号', field: 'bank_account', fixed: ['alias'], type: 'text', removable: true, contractTypes: [type.expenses] },
+    { name: '结算书编号', field: 'settle_code', fixed: ['alias'], type: 'text', removable: true },
+];
+
 
 module.exports = {
     type,
@@ -122,4 +139,5 @@ module.exports = {
     attributeSet,
     defaultAttributeSet,
     defaultAttribute,
+    detailColumnSet,
 };

+ 100 - 2
app/controller/contract_controller.js

@@ -324,8 +324,6 @@ module.exports = app => {
                     renderData.shenpi = ctx.subProject.shenpi;
                 }
 
-                const contractColSet = await ctx.service.contractColSet.getContractColSet(ctx.subProject.id, ctx.contractOptions.tid, ctx.contract_type);
-                renderData.colSet = ctx.service.contractColSet.analysisColSetWithDefine(contractConst.colSet[ctx.contract_type], contractColSet.info, contractConst.defaultColSet[ctx.contract_type]);
                 const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
                 renderData.accountList = accountList;
                 const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
@@ -336,6 +334,66 @@ module.exports = app => {
                 const contractAttributeSet = commonJson && commonJson.contract_attribute ? commonJson.contract_attribute : contractConst.attributeSet;
                 const attributeSet = ctx.service.contractColSet.analysisColSetWithDefine(contractConst.attributeSet, contractAttributeSet, contractConst.defaultAttributeSet);
                 renderData.attributeSet = ctx.helper._.filter(attributeSet, { show: 1 });
+                const detailColumnSet = contractConst.detailColumnSet.filter(item => {
+                    return !item.contractTypes || item.contractTypes.indexOf(ctx.contract_type) >= 0;
+                }).map(item => {
+                    const result = Object.assign({ source: 'detail' }, item);
+                    if (item.field === 'pay_price') {
+                        result.name = ctx.contract_type === contractConst.type.income ? '累计回款(P)' : '累计付款(P)';
+                    } else if (item.field === 'df_price') {
+                        result.name = ctx.contract_type === contractConst.type.income ? '待回款' : '待支付';
+                    }
+                    return result;
+                });
+                const attributeColumnSet = renderData.attributeSet.map(item => {
+                    return Object.assign({}, item, {
+                        name: item.alias || item.name,
+                        source: 'attribute',
+                        fixed: ['alias'],
+                        removable: true,
+                    });
+                });
+                const contractColumnDefine = contractConst.colSet[ctx.contract_type]
+                    .concat(detailColumnSet, attributeColumnSet);
+                const contractColSet = await ctx.service.contractColSet.getContractColSet(ctx.subProject.id, ctx.contractOptions.tid, ctx.contract_type);
+                renderData.colSet = ctx.service.contractColSet.analysisColSetWithDefine(
+                    contractColumnDefine,
+                    contractColSet.info,
+                    contractConst.defaultColSet[ctx.contract_type]
+                );
+                renderData.contractColumnOptions = {
+                    detail: contractConst.colSet[ctx.contract_type]
+                        .filter(item => { return item.removable; })
+                        .map(item => { return Object.assign({ source: 'detail' }, item); })
+                        .concat(detailColumnSet),
+                    attribute: attributeColumnSet,
+                };
+                if (ctx.contractOptions.tid) {
+                    const tenders = await ctx.service.tender.getAllDataByCondition({
+                        where: { spid: ctx.subProject.id },
+                        columns: ['id', 'name'],
+                    });
+                    renderData.contractColCopyScope = 'tender';
+                    renderData.contractColCopyTargets = [{
+                        id: 'sub-project-root', name: ctx.subProject.name, tree_level: 1, is_folder: true,
+                    }].concat(tenders.filter(item => {
+                        return Number(item.id) !== Number(ctx.contractOptions.tid);
+                    }).map(item => {
+                        return { id: item.id, name: item.name, tree_level: 2, is_folder: false };
+                    }));
+                } else {
+                    const subProjects = await ctx.service.subProject.getSubProjectTreeNodes(
+                        ctx.session.sessionProject.id,
+                        ctx.session.sessionUser.accountId,
+                        true
+                    );
+                    renderData.contractColCopyScope = 'project';
+                    renderData.contractColCopyTargets = subProjects.map(item => {
+                        return Object.assign({}, item, {
+                            current: String(item.id) === String(ctx.subProject.id),
+                        });
+                    });
+                }
                 await this.layout('contract/detail.ejs', renderData, 'contract/detail_modal.ejs');
             } catch (err) {
                 ctx.log(err);
@@ -351,9 +409,11 @@ module.exports = app => {
          */
         async colSet(ctx) {
             try {
+                if (!ctx.session.sessionUser.is_admin) throw '无权进行列设置';
                 const colType = ctx.request.body.col_type;
                 const colSet = JSON.parse(ctx.request.body.col_set);
                 const contractType = ctx.request.body.type;
+                if ([contractConst.type.expenses, contractConst.type.income].indexOf(contractType) < 0) throw '合同类型错误';
                 await ctx.service.contractColSet.setContractColSet(ctx.subProject.id, ctx.contractOptions.tid, contractType, colType, colSet);
                 ctx.redirect(ctx.request.header.referer);
             } catch (err) {
@@ -362,6 +422,44 @@ module.exports = app => {
             }
         }
 
+        async copyColSet(ctx) {
+            try {
+                if (!ctx.session.sessionUser.is_admin) throw '无权进行列设置';
+                const data = JSON.parse(ctx.request.body.data);
+                const contractType = Number(data.type);
+                if ([contractConst.type.expenses, contractConst.type.income].indexOf(contractType) < 0) throw '合同类型错误';
+                if (!Array.isArray(data.col_set) || !Array.isArray(data.target_ids)) throw '列设置参数错误';
+                const targetIds = Array.from(new Set(data.target_ids.map(String)));
+                if (!targetIds.length) throw '请选择需要设置的目标';
+
+                let targets = [];
+                if (ctx.contractOptions.tid) {
+                    const tenders = await ctx.service.tender.getAllDataByCondition({
+                        where: { spid: ctx.subProject.id },
+                        columns: ['id'],
+                    });
+                    const validIds = new Set(tenders.map(item => String(item.id)));
+                    targets = targetIds.filter(id => validIds.has(id) && id !== String(ctx.contractOptions.tid))
+                        .map(id => { return { spid: ctx.subProject.id, tid: Number(id) }; });
+                } else {
+                    const subProjects = await ctx.service.subProject.getSubProject(
+                        ctx.session.sessionProject.id,
+                        ctx.session.sessionUser.accountId,
+                        true
+                    );
+                    const validIds = new Set(subProjects.filter(item => !item.is_folder).map(item => String(item.id)));
+                    targets = targetIds.filter(id => validIds.has(id) && id !== String(ctx.subProject.id))
+                        .map(id => { return { spid: id, tid: null }; });
+                }
+                if (targets.length !== targetIds.length) throw '选择的目标不存在或不属于当前范围';
+                await ctx.service.contractColSet.copyContractColSet(targets, contractType, 'info', data.col_set);
+                ctx.body = { err: 0, msg: '列设置复制成功', data: { count: targets.length } };
+            } catch (err) {
+                ctx.log(err);
+                ctx.body = this.ajaxErrorBody(err, '复制列设置失败');
+            }
+        }
+
         async loadDetail(ctx) {
             try {
                 const responseData = {

+ 8 - 3
app/controller/dashboard_controller.js

@@ -147,9 +147,9 @@ module.exports = app => {
                 noticeList.push(...ctx.helper.addKeyValue4ObjArray(spCostStageAnalysis, 'shenpi_type', 'costStageAnalysis'));
                 const spCostStageDuty = subProject.page_show.cost ? ctx.helper._.filter(auditShenpiCostStageDuty, { spid: subProject.id }) : [];
                 noticeList.push(...ctx.helper.addKeyValue4ObjArray(spCostStageDuty, 'shenpi_type', 'costStageDuty'));
-                const spContracts = subProject.page_show.openContract ? ctx.helper._.filter(auditShenpiContracts, { spid: subProject.id }) : [];
+                const spContracts = subProject.page_show.openContract ? ctx.helper._.filter(auditShenpiContracts, c => c.spid === subProject.id || c.tender_spid === subProject.id) : [];
                 noticeList.push(...ctx.helper.addKeyValue4ObjArray(spContracts, 'shenpi_type', 'contract'));
-                const spContractPays = (subProject.page_show.openContractTenderShenpi || subProject.page_show.openContractSubProjectShenpi) ? ctx.helper._.filter(auditShenpiContractPays, { spid: subProject.id }) : [];
+                const spContractPays = (subProject.page_show.openContractTenderShenpi || subProject.page_show.openContractSubProjectShenpi) ? ctx.helper._.filter(auditShenpiContractPays, cp => cp.spid === subProject.id || cp.tender_spid === subProject.id) : [];
                 for (const pay of spContractPays) {
                     pay.shenpi_type = pay.contract_type === 2 ? 'contractReceipt' : 'contractPay';
                 }
@@ -251,6 +251,7 @@ module.exports = app => {
                 acSafeStage: auditConst.safeStage,
                 acPhasePay: auditConst.phasePay,
                 acCostStage: auditConst.costStage,
+                acContract: auditConst.contract,
                 noticeList,
                 noticeDayList,
                 pushType: auditConst.pushType,
@@ -304,7 +305,11 @@ module.exports = app => {
         async auditSet(ctx, allAudits, subProjects, dashboardStatus, type) {
             const audits = [];
             for (const t of allAudits) {
-                const sp = subProjects.find(sp => sp.id === t.spid);
+                let sp = subProjects.find(sp => sp.id === t.spid);
+                // 标段合同的spid为null,通过tender_spid(标段的spid)匹配子项目
+                if (!sp && t.tender_spid) {
+                    sp = subProjects.find(sp => sp.id === t.tender_spid);
+                }
                 if (sp) {
                     t.sp_name = sp.name;
                     t.start_audit = 0;

+ 49 - 20
app/public/js/contract_detail.js

@@ -8,13 +8,13 @@ $(document).ready(function() {
             // 移除所有active类
             $('.bc-bar .nav li a').removeClass('active');
             $('.tab-content .tab-pane').removeClass('active');
-            
+
             // 激活htpay选项卡
             $('.bc-bar .nav li a[href="#htpay"]').addClass('active');
             $('#htpay').addClass('active');
         }, 100);
     }
-    
+
     let stdXmj;
     const ckBillsSpread = '/contract/' + window.location.pathname.split('/')[2] + '/' + contractConst.typeMap[contract_type] + '/detail/contract-billsSelect';
     autoFlashHeight();
@@ -25,10 +25,7 @@ $(document).ready(function() {
             {title: '项目名称/合同名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 360, formatter: '@', readOnly: 'readOnly.code', wordWrap: true},
             {title: '创建人', colSpan: '1', rowSpan: '2', field: 'username', hAlign: 1, width: 80, formatter: '@', readOnly: true},
             {title: '合同金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 120, formatter: '@', readOnly: true},
-            {title: '合同类型', colSpan: '1', rowSpan: '2', field: 'type', hAlign: 0, width: 120, readOnly: 'readOnly.type', cellType: 'customizeCombo', comboItems: types, cellTypeKey: 1},
             {title: '合同状态', colSpan: '1', rowSpan: '2', field: 'status', hAlign: 1, width: 100, formatter: '@', readOnly: true, getValue:'getValue.status', foreColor:'foreColor.status'},
-            {title: '甲方', colSpan: '1', rowSpan: '2', field: 'party_a', hAlign: 1, width: 80, formatter: '@', readOnly: true},
-            {title: '乙方', colSpan: '1', rowSpan: '2', field: 'party_b', hAlign: 1, width: 80, formatter: '@', readOnly: true},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'remark', hAlign: 0, width: 120, formatter: '@', cellType: 'ellipsisAutoTip', scrollHeightClass: '.sjs-height-1'},
         ],
         emptyRows: 0,
@@ -103,7 +100,6 @@ $(document).ready(function() {
             return data.c_code && data.status ? auditConst.auditClassColor[data.status] : '';
         };
     }
-    const gdColField = ['type', 'party_a', 'party_b'];
     const settlePriceEnabled = !!_.find(colSet, { field: 'settle_price', show: 1 });
     const getContractPayLimit = function (node) {
         const useSettlePrice = settlePriceEnabled && parseFloat(node.settle_price) !== 0;
@@ -113,6 +109,9 @@ $(document).ready(function() {
         };
     };
     const colMap = {
+      type: {title: '合同类型', colSpan: '1', rowSpan: '2', field: 'type', hAlign: 0, width: 120, readOnly: 'readOnly.type', cellType: 'customizeCombo', comboItems: types, cellTypeKey: 1},
+      party_a: {title: '甲方', colSpan: '1', rowSpan: '2', field: 'party_a', hAlign: 1, width: 100, formatter: '@', readOnly: true},
+      party_b: {title: '乙方', colSpan: '1', rowSpan: '2', field: 'party_b', hAlign: 1, width: 100, formatter: '@', readOnly: true},
       yf_price: {title: '累计应付', colSpan: '1', rowSpan: '2', field: 'yf_price', hAlign: 2, width: 120, formatter: '@', readOnly: true},
       settle_price: {title: '结算金额', colSpan: '1', rowSpan: '2', field: 'settle_price', hAlign: 2, width: 120, type: 'Number', readOnly: 'readOnly.settlePrice'},
       stackedBar: {title: '应付进度', colSpan: '1', rowSpan: '2', formatter: '@', readOnly: true, field: 'stackedBar', hAlign: 0, width: 200, cellType: 'stackedBar', stackedBarCover: true, bc_type: 'grid', getTip: getStackedBarTip, hintNum: true},
@@ -123,26 +122,50 @@ $(document).ready(function() {
       calc: {title: '计算1', colSpan: '1', rowSpan: '2', field: 'calc', hAlign: 2, width: 120, type: 'Number', readOnly: 'readOnly.calc'},
       calc2: {title: '计算2', colSpan: '1', rowSpan: '2', field: 'calc2', hAlign: 2, width: 120, type: 'Number', readOnly: 'readOnly.calc'},
     }
+    const optionalContractColumns = contractColumnOptions.detail.concat(contractColumnOptions.attribute);
+    optionalContractColumns.forEach(function (item) {
+        if (colMap[item.field]) return;
+        const col = {
+            title: item.name,
+            colSpan: '1',
+            rowSpan: '2',
+            field: item.field,
+            hAlign: item.type === 'number' ? 2 : 0,
+            width: 100,
+            formatter: '@',
+            readOnly: true,
+        };
+        if (item.type === 'number') col.type = 'Number';
+        if (item.source === 'attribute') {
+            contractCol.getValue[item.field] = function (data) {
+                if (!data || !data.attribute_json) return '';
+                try {
+                    const attributes = JSON.parse(data.attribute_json);
+                    return attributes[item.field] === undefined || attributes[item.field] === null ? '' : attributes[item.field];
+                } catch (e) {
+                    return '';
+                }
+            };
+            col.getValue = 'getValue.' + item.field;
+        } else if (item.field === 'df_price') {
+            contractCol.getValue.df_price = function (data) {
+                return data ? ZhCalc.sub(data.yf_price, data.sf_price) : '';
+            };
+            col.getValue = 'getValue.df_price';
+        } else if (item.type === 'date') {
+            contractCol.getValue[item.field] = function (data) {
+                return data && data[item.field] ? moment(data[item.field]).format('YYYY-MM-DD') : '';
+            };
+            col.getValue = 'getValue.' + item.field;
+        }
+        colMap[item.field] = col;
+    });
     // 根据 colSet 组装最终显示的列配置
     colSet.forEach(col => {
         const colInfo = colMap && colMap[col.field];
-        if (col.field === 'settle_price') {
-            if (colInfo && col.show) {
-                const totalPriceIndex = contractSpreadSetting.cols.findIndex(c => c.field === 'total_price');
-                contractSpreadSetting.cols.splice(totalPriceIndex + 1, 0, Object.assign({}, colInfo, { title: col.name }));
-            }
-            return;
-        }
         if (colInfo && col.show) {
             contractSpreadSetting.cols.push(Object.assign({}, colInfo, { title: (col && col.alias) || col.name }));
         }
-        if (col.gd) {
-            const gdColInfo = gdColField.includes(col.field);
-            if (gdColInfo && !col.show) {
-                const index = contractSpreadSetting.cols.findIndex(c => c.field === col.field);
-                if (index !== -1) contractSpreadSetting.cols.splice(index, 1);
-            }
-        }
     });
 
 
@@ -673,6 +696,8 @@ $(document).ready(function() {
                                         <td>${pay.yf_price}</td>
                                         <td>${pay.sf_price}</td>
                                         <td>${pay.pay_type}</td>
+                                        <td>${pay.kp_price ? pay.kp_price : ''}</td>
+                                        <td>${pay.kp_time ? moment(pay.kp_time).format('YYYY-MM-DD') : ''}</td>
                                         <td>${pay.username}</td>
                                         <td>${moment(pay.create_time).format('YYYY-MM-DD HH:mm:ss')}</td>
                                         <td>${pay.remark}</td>
@@ -3315,6 +3340,8 @@ $(document).ready(function() {
             yf_price: $('#cons-addpay input[name="yf_price"]').val() || 0,
             sf_price: $('#cons-addpay input[name="sf_price"]').val() || 0,
             pay_type: $('#cons-addpay select[name="pay_type"]').val(),
+            kp_price: $('#cons-addpay input[name="kp_price"]').val(),
+            kp_time: $('#cons-addpay input[name="kp_time"]').val(),
             remark: $('#cons-addpay textarea[name="remark"]').val(),
         }
         const name = contract_type === contractConst.type.expenses ? '付' : '回';
@@ -3370,6 +3397,8 @@ $(document).ready(function() {
             yf_price: $('#cons-addpay input[name="yf_price"]').val() || 0,
             sf_price: $('#cons-addpay input[name="sf_price"]').val() || 0,
             pay_type: $('#cons-addpay select[name="pay_type"]').val(),
+            kp_price: $('#cons-addpay input[name="kp_price"]').val(),
+            kp_time: $('#cons-addpay input[name="kp_time"]').val(),
             remark: $('#cons-addpay textarea[name="remark"]').val(),
         }
         const name = contract_type === contractConst.type.expenses ? '付' : '回';

+ 3 - 2
app/router.js

@@ -338,6 +338,8 @@ module.exports = app => {
     app.post('/sp/:id/contract/tender/:tid/detail/:type/:cid/pay/:cpid/file/delete', sessionAuth, subProjectCheck, contractCheck, 'contractController.deleteFile');
     app.get('/sp/:id/contract/tender/:tid/detail/:type/:cid/pay/:cpid/file/:fid/download', sessionAuth, subProjectCheck, contractCheck, 'contractController.downloadFile');
     app.post('/sp/:id/contract/tender/:tid/col-set', sessionAuth, subProjectCheck, contractCheck, 'contractController.colSet');
+    app.post('/sp/:id/contract/tender/:tid/col-set/copy', sessionAuth, subProjectCheck, contractCheck, 'contractController.copyColSet');
+
     app.post('/sp/:id/contract/tender/:tid/detail/check', sessionAuth, subProjectCheck, contractCheck, 'contractController.checkAudit');
     app.post('/sp/:id/contract/tender/:tid/detail/:type/check', sessionAuth, subProjectCheck, contractCheck, 'contractController.checkAudit');
     app.post('/sp/:id/contract/tender/:tid/detail/:type/:cid/supplement/:csid/file/upload', sessionAuth, subProjectCheck, contractCheck, 'contractController.uploadFile');
@@ -367,6 +369,7 @@ module.exports = app => {
     app.post('/sp/:id/contract/detail/check', sessionAuth, subProjectCheck, contractCheck, 'contractController.checkAudit');
     app.post('/sp/:id/contract/detail/:type/check', sessionAuth, subProjectCheck, contractCheck, 'contractController.checkAudit');
     app.post('/sp/:id/contract/col-set', sessionAuth, subProjectCheck, contractCheck, 'contractController.colSet');
+    app.post('/sp/:id/contract/col-set/copy', sessionAuth, subProjectCheck, contractCheck, 'contractController.copyColSet');
     app.get('/sp/:id/contract/setting', sessionAuth, subProjectCheck, 'contractController.setting');
     app.get('/sp/:id/contract/tender/setting', sessionAuth, subProjectCheck, 'contractController.setting');
     app.get('/sp/:id/contract/setting/shenpi', sessionAuth, subProjectCheck, 'contractController.shenpiSet');
@@ -378,8 +381,6 @@ module.exports = app => {
     app.post('/sp/:id/contract/detail/:type/ai/upload', sessionAuth, subProjectCheck, contractCheck, 'contractController.uploadAndAi');
     app.post('/sp/:id/contract/detail/ai/edit', sessionAuth, subProjectCheck, contractCheck, 'contractController.editAndAi');
     app.post('/sp/:id/contract/detail/:type/ai/edit', sessionAuth, subProjectCheck, contractCheck, 'contractController.editAndAi');
-
-
     // 资料归集-列表
     app.get('/file', sessionAuth, 'fileController.index');
     // 资料归集-模板

+ 35 - 18
app/service/contract_col_set.js

@@ -59,6 +59,33 @@ module.exports = app => {
             await this.defaultUpdate(data, { where: { spid, tid, type } });
         }
 
+        async copyContractColSet(targets, type, colSetType, colSet) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const updateData = {};
+                updateData[colSetType] = JSON.stringify(colSet);
+                for (const target of targets) {
+                    const rows = await transaction.select(this.tableName, {
+                        where: { spid: target.spid, tid: target.tid, type },
+                        limit: 1,
+                    });
+                    if (rows.length) {
+                        await transaction.update(this.tableName, updateData, { where: { id: rows[0].id } });
+                    } else {
+                        await transaction.insert(this.tableName, Object.assign({
+                            spid: target.spid,
+                            tid: target.tid,
+                            type,
+                        }, updateData));
+                    }
+                }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
         analysisColSetWithDefine(colSetDefine, colSet, defaultColSet) {
             const result = [];
             const colSetDefineMap = new Map(); // 用 Map 优化字段查找(O(1) 复杂度)
@@ -66,36 +93,26 @@ module.exports = app => {
             colSetDefine.forEach(csd => {
                 colSetDefineMap.set(csd.field, csd);
             });
-            const gdResult = []; // 固定列结果,最后合并到 result 前面
             for (const cs of colSet) {
               const field = cs.field;
+              if (colSetFieldSet.has(field)) continue;
               const csd = colSetDefineMap.get(field); // 从字段定义中取 name、fixed
               const dcs = defaultColSet.find(x => x.field === field); // 取默认配置(兜底)
 
               if (csd) {
                   // 合并:colSetDefine(name、fixed)> colSet(show、alias、顺序)> defaultColSet(兜底)
-                  if (csd.gd) {
-                      gdResult.push({ ...dcs, ...cs, ...csd }); // 固定列先保存到 gdResult,最后合并到 result 前面
-                  } else {
-                      result.push({ ...dcs, ...cs, ...csd });
-                  }
+                  result.push({ ...dcs, ...cs, ...csd });
               }
               colSetFieldSet.add(field); // 标记该字段已处理
             }
-            // 按 colSetDefine 中未处理的顺序,补充到结果末尾
-            for (const csd of colSetDefine) {
-                const field = csd.field;
-                if (!colSetFieldSet.has(field)) { // 未在 colSet 中找到的字段
-                    const dcs = defaultColSet.find(x => x.field === field); // 取默认配置
-                    if (dcs.gd) { // 补充到结果头中
-                        gdResult.push({ ...dcs, ...csd });
-                    } else {
-                        result.push({ ...dcs, ...csd });
-                    }
+            // 仅补充系统必备默认列。可移除列及管理员尚未添加的属性不自动补回。
+            for (const dcs of defaultColSet) {
+                const field = dcs.field;
+                const csd = colSetDefineMap.get(field);
+                if (!colSetFieldSet.has(field) && csd && !csd.removable) {
+                    result.push({ ...dcs, ...csd });
                 }
             }
-            // 合并固定列和非固定列,固定列在前
-            result.unshift(...gdResult);
             return result;
         }
     }

+ 2 - 0
app/service/contract_pay.js

@@ -104,6 +104,8 @@ module.exports = app => {
                     yf_price: data.yf_price,
                     sf_price: data.sf_price,
                     pay_type: data.pay_type,
+                    kp_price: data.kp_price,
+                    kp_time: data.kp_time,
                     remark: data.remark,
                     create_time: new Date(),
                     need_shenpi: options.spid ? this.ctx.subProject.page_show.openContractPaySubProjectShenpi : this.ctx.subProject.page_show.openContractPayTenderShenpi,

Plik diff jest za duży
+ 26 - 8
app/service/contract_sp_audit.js


+ 191 - 140
app/view/contract/col_set.ejs

@@ -1,162 +1,213 @@
 <!--弹出列设置-->
+<style>
+    #col-set .selected-contract-cols table {
+        margin-bottom: 0;
+        border-collapse: separate;
+        border-spacing: 0;
+        border: 0;
+    }
+    #col-set .selected-contract-cols .table-bordered th,
+    #col-set .selected-contract-cols .table-bordered td {
+        border: 0;
+        border-right: 1px solid #dee2e6;
+        border-bottom: 1px solid #dee2e6;
+    }
+    #col-set .selected-contract-cols .table-bordered th:last-child,
+    #col-set .selected-contract-cols .table-bordered td:last-child {
+        border-right: 0;
+    }
+    #col-set .selected-contract-cols tbody tr:last-child td {
+        border-bottom: 0;
+    }
+    #col-set .selected-contract-cols thead th {
+        position: sticky;
+        top: 0;
+        z-index: 2;
+        background-color: #f8f9fa;
+    }
+    #copy-contract-col-set { z-index: 1060; }
+    #copy-contract-col-set .copy-target-list { max-height: 420px; overflow-y: auto; }
+    #copy-contract-col-set .copy-target-row { min-height: 34px; }
+</style>
 <div class="modal fade" id="col-set" data-backdrop="static">
-    <div class="modal-dialog" role="document">
+    <div class="modal-dialog modal-xl" role="document">
         <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">列设置</h5>
+            <div class="modal-header"><h5 class="modal-title">列设置</h5></div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-md-4">
+                        <div class="font-weight-bold mb-2">可添加的合同属性</div>
+                        <div class="contract-col-options border rounded p-2" style="height: 520px; overflow-y: scroll; scrollbar-gutter: stable;">
+                            <% const columnGroups = [
+                                { name: '合同详情', items: contractColumnOptions.detail },
+                                { name: '附加属性', items: contractColumnOptions.attribute },
+                            ]; %>
+                            <% for (const group of columnGroups) { %>
+                                <div class="contract-col-group mb-3">
+                                    <div class="bg-light font-weight-bold px-2 py-1 mb-1"><%- group.name %></div>
+                                    <% if (group.items.length === 0) { %><div class="text-muted px-2 py-1">暂无可用属性</div><% } %>
+                                    <% for (const item of group.items) { %>
+                                        <div class="contract-col-option d-flex align-items-center justify-content-between px-2 py-1"
+                                             data-field="<%- item.field %>" data-name="<%- item.name %>" data-source="<%- item.source %>">
+                                            <span><%- item.name %></span>
+                                            <button type="button" class="btn btn-sm btn-outline-primary add-contract-col">添加</button>
+                                        </div>
+                                    <% } %>
+                                </div>
+                            <% } %>
+                        </div>
+                    </div>
+                    <div class="col-md-8">
+                        <div class="font-weight-bold mb-2">已选择的列</div>
+                        <div class="selected-contract-cols table-responsive border rounded" style="height: 520px; overflow-y: scroll; scrollbar-gutter: stable;">
+                            <table class="table table-hover table-bordered mb-0">
+                                <thead><tr><th width="150px" class="text-center">列名</th><th width="60px" class="text-center">显示</th><th width="150px" class="text-center">别名</th><th width="170px" class="text-center">操作</th></tr></thead>
+                                <tbody class="text-center">
+                                <% for (const cs of colSet) { %>
+                                    <tr code="<%- cs.field %>" data-name="<%- cs.name %>" data-source="<%- cs.source || 'base' %>" data-removable="<%- cs.removable ? 1 : 0 %>">
+                                        <td><%- cs.name %><% if (cs.hint) { %><i class="fa fa-question-circle text-primary ml-1" data-placement="bottom" data-toggle="tooltip" data-original-title="<%- cs.hint %>"></i><% } %></td>
+                                        <td><div class="form-check form-check-inline"><input class="form-check-input col-show" type="checkbox" <% if (cs.show) { %>checked<% } %>></div></td>
+                                        <% if (cs.fixed.indexOf('alias') >= 0) { %><td class="disabled">-</td><% } else { %><td><input type="text" class="form-control form-control-sm col-alias" value="<%- cs.alias %>"></td><% } %>
+                                        <td class="text-nowrap">
+                                            <a href="javascript:;" class="move-up text-primary mr-2">上移</a>
+                                            <a href="javascript:;" class="move-down text-primary mr-2">下移</a>
+                                            <% if (cs.removable) { %><a href="javascript:;" class="remove-contract-col text-danger">移除</a><% } %>
+                                        </td>
+                                    </tr>
+                                <% } %>
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                </div>
             </div>
+            <form class="modal-footer justify-content-between" action="/sp/<%- ctx.subProject.id %>/contract/<%- ctx.contractOptions.tid ? 'tender/'+ctx.contractOptions.tid + '/' : '' %>col-set" method="post" onsubmit="return onSetCol();">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>"><input type="hidden" name="col_set" value=""><input type="hidden" name="type" value="<%- ctx.contract_type %>"><input type="hidden" name="col_type" value="info">
+                <button type="button" class="btn btn-outline-primary btn-sm" id="open-copy-contract-col-set">设置至其他<%- contractColCopyScope === 'tender' ? '标段' : '项目' %></button>
+                <div><button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button><button type="submit" class="btn btn-primary btn-sm ml-1" id="add-bd-ok">确定修改</button></div>
+            </form>
+        </div>
+    </div>
+</div>
+<div class="modal fade" id="copy-contract-col-set" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header"><h5 class="modal-title">设置至其他<%- contractColCopyScope === 'tender' ? '标段' : '项目' %></h5></div>
             <div class="modal-body">
-                <table class="table table-hover table-bordered">
-                    <thead><tr><th width="150px" class="text-center">列名</th><th width="50px" class="text-center">显示</th><th width="150px" class="text-center">别名</th><th width="80px" class="text-center">操作</th></tr></thead>
-                    <tbody class="text-center">
-                    <% for (const cs of colSet) { %>
-                      <tr code="<%- cs.field %>">
-                          <td>
-                              <%- cs.name %>
-                              <% if (cs.hint) { %>
-                                  <i class="fa fa-question-circle text-primary ml-1" data-placement="bottom" data-toggle="tooltip" data-original-title="<%- cs.hint %>"></i>
-                              <% } %>
-                          </td>
-                          <% if (cs.fixed.indexOf('show') >= 0 ) { %>
-                          <td>
-                              <div class="form-check form-check-inline">
-                                  <input class="form-check-input" type="checkbox" id="inlineCheckbox-<%- cs.code %>" checked="" disabled="">
-                                  <label class="form-check-label" for="inlineCheckbox-<%- cs.code %>"></label>
-                              </div>
-                          </td>
-                          <% } else { %>
-                          <td>
-                              <div class="form-check form-check-inline">
-                                  <input class="form-check-input" type="checkbox" id="inlineCheckbox-<%- cs.code %>" <% if (cs.show) { %> checked <% } %> >
-                                  <label class="form-check-label" for="inlineCheckbox-<%- cs.code %>"></label>
-                              </div>
-                          </td>
-                          <% } %>
-                          <% if (cs.fixed.indexOf('alias') >= 0) { %>
-                          <td class="disabled">-</td>
-                          <% } else {%>
-                          <td><input type="text" class="form-control form-control-sm" value="<%- cs.alias %>"></td>
-                          <% } %>
-                          <td>
-                              <% if (!cs.gd) { %>
-                              <a href="javascript:;" class="move-up text-primary mr-2" style="text-decoration: none;">上移</a>
-                              <a href="javascript:;" class="move-down text-primary" style="text-decoration: none;">下移</a>
-                              <% } %>
-                          </td>
-                      </tr>
-                      <% } %>
-                    </tbody>
-                </table>
+                <div class="alert alert-warning py-2">将当前“<%- ctx.contract_type === contractConst.type.income ? '收入合同' : '支出合同' %>”列设置复制到勾选的<%- contractColCopyScope === 'tender' ? '标段' : '子项目' %>。</div>
+                <div class="border rounded copy-target-list">
+                    <% if (!contractColCopyTargets.some(item => !item.is_folder && !item.current)) { %><div class="text-muted p-3">暂无其他可设置的<%- contractColCopyScope === 'tender' ? '标段' : '子项目' %></div><% } %>
+                    <% for (const target of contractColCopyTargets) { %>
+                        <div class="copy-target-row d-flex align-items-center border-bottom px-2"
+                             style="padding-left: <%- 12 + ((target.tree_level || 1) - 1) * 24 %>px !important;">
+                            <% if (target.is_folder) { %>
+                                <i class="fa fa-folder-o text-warning mr-2"></i><span><%- target.name %></span>
+                            <% } else { %>
+                                <label class="mb-0 w-100">
+                                    <input type="checkbox" class="copy-contract-col-target mr-2" value="<%- target.id %>" <%- target.current ? 'disabled' : '' %>>
+                                    <i class="fa <%- contractColCopyScope === 'tender' ? 'fa-flag-o' : 'fa-file-o' %> text-muted mr-1"></i><%- target.name %><% if (target.current) { %><span class="text-muted ml-2">(当前)</span><% } %>
+                                </label>
+                            <% } %>
+                        </div>
+                    <% } %>
+                </div>
             </div>
-            <form class="modal-footer" action="/sp/<%- ctx.subProject.id %>/contract/<%- ctx.contractOptions.tid ? 'tender/'+ctx.contractOptions.tid + '/' : '' %>col-set" method="post" onsubmit="return onSetCol();">
-                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
-                <input type="hidden" name="col_set" value="">
-                <input type="hidden" name="type" value="<%- ctx.contract_type %>">
-                <input type="hidden" name="col_type" value="info">
+            <div class="modal-footer">
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="submit" class="btn btn-primary btn-sm" id="add-bd-ok">确定修改</button>
-            </form>
+                <button type="button" class="btn btn-primary btn-sm" id="copy-contract-col-set-ok">确认复制</button>
+            </div>
         </div>
     </div>
 </div>
 <script>
-// 立即获取表格 tbody 并绑定事件(确保脚本加载时即可初始化)
-const $tbody = $('#col-set').find('.modal-body table tbody');
-
-// 保存初始 HTML,在关闭时恢复(保证再次打开时为初始状态)
-const _initialColSetTbodyHtml = $tbody.html();
-
-// 更新上下移链接显示状态
-function updateMoveButtons() {
-    const $movableRows = $tbody.find('tr').filter(function() {
-        return $(this).find('.move-up, .move-down').length > 0;
-    });
-    const totalRows = $movableRows.length;
-
-    $movableRows.each(function(index) {
-        const $row = $(this);
-        const $upLink = $row.find('.move-up');
-        const $downLink = $row.find('.move-down');
-
-        if (totalRows === 1) {
-            $upLink.hide();
-            $downLink.hide();
-        } else if (index === 0) {
-            $upLink.hide();
-            $downLink.show();
-        } else if (index === totalRows - 1) {
-            $upLink.show();
-            $downLink.hide();
+const $colSetModal = $('#col-set');
+const $colSetTbody = $colSetModal.find('tbody');
+const _initialColSetTbodyHtml = $colSetTbody.html();
+const _initialColOptionsHtml = $colSetModal.find('.contract-col-options').html();
+
+function syncContractColOptions() {
+    const selected = {};
+    $colSetTbody.find('tr').each(function () { selected[$(this).attr('code')] = true; });
+    $colSetModal.find('.contract-col-option').each(function () {
+        const $option = $(this);
+        if (selected[$option.data('field')]) {
+            $option.removeClass('d-flex').addClass('d-none');
         } else {
-            $upLink.show();
-            $downLink.show();
+            $option.removeClass('d-none').addClass('d-flex');
         }
     });
 }
-
-// 立即初始化链接状态(页面渲染完成后就计算显示)
-updateMoveButtons();
-
-// 若模态框打开后表格有异步或动态变更,打开时再次校验状态
-$('#col-set').on('shown.bs.modal', function() {
-    updateMoveButtons();
-});
-
-// 模态框隐藏时恢复到初始内容并重新绑定必要状态
-$('#col-set').on('hidden.bs.modal', function() {
-    // 恢复初始 tbody 内容
-    $tbody.html(_initialColSetTbodyHtml);
-    // 重新计算按钮显示状态
-    updateMoveButtons();
-});
-
-// 事件委托:从一开始就绑定上/下移事件
-$tbody.on('click', '.move-up', function(e) {
-    e.preventDefault();
-    const $currentRow = $(this).closest('tr');
-    const $prevRow = $currentRow.prev('tr');
-
-    if ($prevRow.find('.move-up, .move-down').length) {
-        $currentRow.insertBefore($prevRow);
-        updateMoveButtons();
-    }
+function updateMoveButtons() {
+    const $rows = $colSetTbody.find('tr');
+    $rows.each(function(index) {
+        $(this).find('.move-up').toggle(index > 0);
+        $(this).find('.move-down').toggle(index < $rows.length - 1);
+    });
+}
+function makeContractColRow(field, name, source) {
+    const $row = $('<tr>').attr({ code: field, 'data-name': name, 'data-source': source, 'data-removable': '1' });
+    $('<td>').text(name).appendTo($row);
+    $('<td>').append($('<div class="form-check form-check-inline">').append($('<input class="form-check-input col-show" type="checkbox" checked>'))).appendTo($row);
+    $('<td class="disabled">').text('-').appendTo($row);
+    $('<td class="text-nowrap">').append(
+        $('<a href="javascript:;" class="move-up text-primary mr-2">').text('上移'),
+        $('<a href="javascript:;" class="move-down text-primary mr-2">').text('下移'),
+        $('<a href="javascript:;" class="remove-contract-col text-danger">').text('移除')
+    ).appendTo($row);
+    return $row;
+}
+$colSetModal.on('shown.bs.modal', function() { syncContractColOptions(); updateMoveButtons(); });
+$colSetModal.on('hidden.bs.modal', function() {
+    $colSetTbody.html(_initialColSetTbodyHtml);
+    $colSetModal.find('.contract-col-options').html(_initialColOptionsHtml);
+    syncContractColOptions(); updateMoveButtons();
 });
-
-$tbody.on('click', '.move-down', function(e) {
-    e.preventDefault();
-    const $currentRow = $(this).closest('tr');
-    const $nextRow = $currentRow.next('tr');
-
-    if ($nextRow.find('.move-up, .move-down').length) {
-        $currentRow.insertAfter($nextRow);
-        updateMoveButtons();
-    }
+$colSetModal.on('click', '.add-contract-col', function() {
+    const $option = $(this).closest('.contract-col-option');
+    const field = $option.data('field');
+    if ($colSetTbody.find('tr[code="' + field + '"]').length) return;
+    $colSetTbody.append(makeContractColRow(field, $option.data('name'), $option.data('source')));
+    syncContractColOptions(); updateMoveButtons();
 });
-
+$colSetTbody.on('click', '.remove-contract-col', function(e) { e.preventDefault(); $(this).closest('tr').remove(); syncContractColOptions(); updateMoveButtons(); });
+$colSetTbody.on('click', '.move-up', function(e) { e.preventDefault(); const $row = $(this).closest('tr'), $prev = $row.prev('tr'); if ($prev.length) $row.insertBefore($prev); updateMoveButtons(); });
+$colSetTbody.on('click', '.move-down', function(e) { e.preventDefault(); const $row = $(this).closest('tr'), $next = $row.next('tr'); if ($next.length) $row.insertAfter($next); updateMoveButtons(); });
+syncContractColOptions(); updateMoveButtons();
 function onSetCol() {
-    const $tbody = $('#col-set').find('.modal-body table tbody');
-    const $currentRows = $tbody.find('tr');
+    const colSet = getCurrentContractColSet();
+    $colSetModal.find('input[name=col_set]').val(JSON.stringify(colSet));
+    return true;
+}
+function getCurrentContractColSet() {
     const colSet = [];
-
-    $currentRows.each(function() {
-        const $row = $(this);
-        let alias = '';
-        const $aliasInput = $row.find('input[type="text"]');
-        if ($aliasInput.length) {
-            alias = $aliasInput.val().trim();
-        }
-
-        // 仅组装数据库需要的3个字段
-        const rowData = {
-            field: $row.attr('code'), // 字段标识(必填)
-            show: Number($row.find('.form-check-input').is(':checked')), // 是否显示(必填)
-            alias: alias // 别名(必填,固定列保存"-")
-        };
-
-        colSet.push(rowData); // 所有行按顺序存入数组,顺序=表格排序
+    $colSetTbody.find('tr').each(function() {
+        const $row = $(this), $alias = $row.find('.col-alias');
+        colSet.push({ field: $row.attr('code'), show: Number($row.find('.col-show').is(':checked')), alias: $alias.length ? $alias.val().trim() : '' });
     });
-
-    $('input[name=col_set]').val(JSON.stringify(colSet));
-    return true;
+    return colSet;
 }
+$('#open-copy-contract-col-set').on('click', function() {
+    $('#copy-contract-col-set').find('.copy-contract-col-target').prop('checked', false);
+    $('#copy-contract-col-set').modal('show');
+});
+$('#copy-contract-col-set').on('shown.bs.modal', function() {
+    const $backdrops = $('.modal-backdrop');
+    $backdrops.last().css('z-index', 1055);
+});
+$('#copy-contract-col-set').on('hidden.bs.modal', function() {
+    if ($colSetModal.hasClass('show')) $('body').addClass('modal-open');
+});
+$('#copy-contract-col-set-ok').on('click', function() {
+    const targetIds = $('#copy-contract-col-set .copy-contract-col-target:checked').map(function() { return this.value; }).get();
+    if (!targetIds.length) { toastr.warning('请选择需要设置的<%- contractColCopyScope === 'tender' ? '标段' : '子项目' %>'); return; }
+    const url = '/sp/<%- ctx.subProject.id %>/contract/<%- ctx.contractOptions.tid ? 'tender/' + ctx.contractOptions.tid + '/' : '' %>col-set/copy';
+    postData(url, {
+        type: <%- ctx.contract_type %>,
+        col_set: getCurrentContractColSet(),
+        target_ids: targetIds,
+    }, function(data, msg) {
+        $('#copy-contract-col-set').modal('hide');
+        toastr.success(msg || ('已设置至' + data.count + '个目标'));
+    });
+});
 </script>

Plik diff jest za duży
+ 3 - 4
app/view/contract/detail.ejs


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

@@ -516,6 +516,14 @@
                     </select>
                 </div>
                 <div class="form-group">
+                      <label>开票金额</label>
+                    <input class="form-control form-control-sm" name="kp_price" placeholder="请输入开票金额" type="number">
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>开票时间</label>
+                    <input class="datepicker-here form-control form-control-sm" name="kp_time" placeholder="点击选择时间" data-date-format="yyyy-MM-dd" data-language="zh" type="text" autocomplete="off">
+                </div>
+                <div class="form-group">
                     <label>备注</label>
                     <textarea class="form-control form-control-sm" name="remark" rows="3"></textarea>
                 </div>

+ 10 - 9
app/view/dashboard/index.ejs

@@ -421,16 +421,16 @@
                                                 allContractItems.push({
                                                     ...contract,
                                                     itemType: 'contract',
-                                                    name: contract.name,
+                                                    name: (contract.tid && contract.tid !== 0)
+                                                        ? '标段合同-' + contract.name
+                                                        : '项目合同-' + contract.name,
                                                     dataType: '20'
                                                 });
                                             });
                                             auditContractPays.forEach(pay => {
-                                                // 标段合同:标段名称 + 空格 + 合同名称
-                                                // 项目合同:项目名称 + 空格 + 合同名称
                                                 const displayName = (!pay.is_project_contract && pay.tender_name) 
-                                                    ? pay.tender_name + ' ' + pay.contract_name 
-                                                    : (pay.project_name ? pay.project_name + ' ' + pay.contract_name : pay.contract_name);
+                                                    ? '标段合同' + '-' + pay.contract_name 
+                                                    : '项目合同-' + pay.contract_name;
                                                 allContractItems.push({
                                                     ...pay,
                                                     itemType: 'pay',
@@ -441,14 +441,15 @@
                                         %>
                                         <% for (const item of allContractItems) { %>
                                             <% 
-                                                // 判断是否需要显示"重新上报"按钮
-                                                // 1. status=checkNo(4) - 被退回状态
-                                                // 2. status=uncheck(1) 且 times>1 - 退回后重新上报待处理状态
                                                 const isCheckNo = item.status === acContract.status.checkNo;
                                                 const isUncheckReSubmit = item.status === acContract.status.uncheck && item.times > 1;
                                                 const showResubmitBtn = isCheckNo || isUncheckReSubmit;
                                             %>
-                                            <% const detailUrl = '/sp/' + ctx.subProject.id + '/contract/detail' + (item.contract_type === 2 ? '/income' : '') + (item.itemType === 'pay' ? '?tab=pay' : ''); %>
+                                            <% const isTenderContract = item.tid && item.tid !== 0 && !item.is_project_contract;
+                                               const incomePath = item.contract_type === 2 ? '/income' : '';
+                                               const detailUrl = isTenderContract
+                                                   ? '/sp/' + ctx.subProject.id + '/contract/tender/' + item.tid + '/detail' + incomePath + (item.itemType === 'pay' ? '?tab=pay' : '')
+                                                   : '/sp/' + ctx.subProject.id + '/contract/detail' + incomePath + (item.itemType === 'pay' ? '?tab=pay' : ''); %>
                                             <tr data-type="<%= item.dataType %>">
                                                 <td><span class="bg-new-20 text-new-20 badge text-width">合同管理</span></td>
                                                 <td>

+ 21 - 60
app/view/dashboard/workspace.ejs

@@ -531,59 +531,20 @@
                                                             <td><%- (am.begin_time ? ctx.moment(am.begin_time).format('YYYY/MM/DD HH:mm') : '') %></td>
                                                             <td><a href="/sp/<%- am.spid %>/cost/tender/<%- am.tid %>/duty/1/stage" class="btn btn-sm btn-table <%- (am.audit_order === 0 ? 'btn-outline-warning text-warning' : 'btn-outline-primary') %>" role="button"><%- (am.audit_order === 0 ? '重新上报' : '审批')%></a></td>
                                                         </tr>
-                                                    <% } else if (db.shenpi_type === 'contract') { %>
-                                                        <% const ct = db; %>
-                                                        <% const ctDetailUrl = '/sp/' + ct.spid + '/contract/detail' + (ct.contract_type === 2 ? '/income' : ''); %>
+                                                    <% } else if (db.shenpi_type === 'contract' || db.shenpi_type === 'contractPay' || db.shenpi_type === 'contractReceipt') { %>
+                                                        <% const c = db; %>
+                                                        <% const cSpId = c.spid || c.tender_spid; %>
+                                                        <% const cDetailUrl = c.is_project_contract ? '/sp/' + c.spid + '/contract/detail' + (c.contract_type === 2 ? '/income' : '') + (c.shenpi_type !== 'contract' ? '?tab=pay' : '') : '/sp/' + c.tender_spid + '/contract/tender/' + c.tid + '/detail' + (c.contract_type === 2 ? '/income' : '') + (c.shenpi_type !== 'contract' ? '?tab=pay' : ''); %>
+                                                        <% const cDisplayName = c.is_project_contract ? '项目合同-' + (c.contract_name || c.name) : '标段合同-' + (c.contract_name || c.name); %>
+                                                        <% const cShowResubmitBtn = c.status === acContract.status.checkNo || (c.status === acContract.status.uncheck && c.times > 1); %>
                                                         <tr data-type="contract">
-                                                            <% if (ct.start_audit === 2) { %><td class="text-center text-danger"><i class="fa fa-exclamation-triangle"></i></td><% } else if (ct.start_audit === 1) { %><td class="text-center text-warning"><i class="fa fa-bell"></i></td><% } else { %><td></td><% } %>
-                                                            <td><span class="badge text-width" style="background-color: #e3f2fd; color: #1976d2;">合同审批</span></td>
-                                                            <td><a href="/sp/<%- ct.spid %>/dashboard"><%- ct.sp_name || '项目合同' %></a></td>
-                                                            <td>
-                                                                <% if (ct.is_project_contract) { %>
-                                                                    <a href="<%= ctDetailUrl %>"><%- ct.name %></a>
-                                                                <% } else { %>
-                                                                    <a href="/tender/<%- ct.tid %>"><%- ct.tender_name %></a> - <a href="<%= ctDetailUrl %>"><%- ct.name %></a>
-                                                                <% } %>
-                                                            </td>
-                                                            <td><%- ct.status_text %></td>
-                                                            <td><%- (ct.begin_time ? ctx.moment(ct.begin_time).format('YYYY/MM/DD HH:mm') : '') %></td>
-                                                            <td><a href="<%= ctDetailUrl %>" class="btn btn-outline-primary btn-sm btn-table" role="button">审批</a></td>
-                                                        </tr>
-                                                    <% } else if (db.shenpi_type === 'contractPay') { %>
-                                                        <% const cp = db; %>
-                                                        <% const cpDetailUrl = '/sp/' + cp.spid + '/contract/detail' + (cp.contract_type === 2 ? '/income' : '') + '?tab=pay'; %>
-                                                        <tr data-type="contract">
-                                                            <% if (cp.start_audit === 2) { %><td class="text-center text-danger"><i class="fa fa-exclamation-triangle"></i></td><% } else if (cp.start_audit === 1) { %><td class="text-center text-warning"><i class="fa fa-bell"></i></td><% } else { %><td></td><% } %>
-                                                            <td><span class="badge text-width" style="background-color: #fff3e0; color: #f57c00;">合同支付</span></td>
-                                                            <td><a href="/sp/<%- cp.spid %>/dashboard"><%- cp.sp_name %></a></td>
-                                                            <td>
-                                                                <% if (cp.is_project_contract) { %>
-                                                                    <a href="<%= cpDetailUrl %>"><%- cp.contract_name %></a> - 第<%- cp.times %>期
-                                                                <% } else { %>
-                                                                    <a href="/tender/<%- cp.tid %>"><%- cp.tender_name %></a> - <a href="<%= cpDetailUrl %>"><%- cp.contract_name %></a> - 第<%- cp.times %>期
-                                                                <% } %>
-                                                            </td>
-                                                            <td>支付</td>
-                                                            <td><%- (cp.begin_time ? ctx.moment(cp.begin_time).format('YYYY/MM/DD HH:mm') : '') %></td>
-                                                            <td><a href="<%= cpDetailUrl %>" class="btn btn-outline-primary btn-sm btn-table" role="button">审批</a></td>
-                                                        </tr>
-                                                    <% } else if (db.shenpi_type === 'contractReceipt') { %>
-                                                        <% const cr = db; %>
-                                                        <% const crDetailUrl = '/sp/' + cr.spid + '/contract/detail' + (cr.contract_type === 2 ? '/income' : '') + '?tab=pay'; %>
-                                                        <tr data-type="contract">
-                                                            <% if (cr.start_audit === 2) { %><td class="text-center text-danger"><i class="fa fa-exclamation-triangle"></i></td><% } else if (cr.start_audit === 1) { %><td class="text-center text-warning"><i class="fa fa-bell"></i></td><% } else { %><td></td><% } %>
-                                                            <td><span class="badge text-width" style="background-color: #e8f5e9; color: #388e3c;">合同回款</span></td>
-                                                            <td><a href="/sp/<%- cr.spid %>/dashboard"><%- cr.sp_name %></a></td>
-                                                            <td>
-                                                                <% if (cr.is_project_contract) { %>
-                                                                    <a href="<%= crDetailUrl %>"><%- cr.contract_name %></a> - 第<%- cr.times %>期
-                                                                <% } else { %>
-                                                                    <a href="/tender/<%- cr.tid %>"><%- cr.tender_name %></a> - <a href="<%= crDetailUrl %>"><%- cr.contract_name %></a> - 第<%- cr.times %>期
-                                                                <% } %>
-                                                            </td>
-                                                            <td>回款</td>
-                                                            <td><%- (cr.begin_time ? ctx.moment(cr.begin_time).format('YYYY/MM/DD HH:mm') : '') %></td>
-                                                            <td><a href="<%= crDetailUrl %>" class="btn btn-outline-primary btn-sm btn-table" role="button">审批</a></td>
+                                                            <% if (c.start_audit === 2) { %><td class="text-center text-danger"><i class="fa fa-exclamation-triangle"></i></td><% } else if (c.start_audit === 1) { %><td class="text-center text-warning"><i class="fa fa-bell"></i></td><% } else { %><td></td><% } %>
+                                                            <td><span class="badge text-width" style="background-color: #e3f2fd; color: #1976d2;">合同管理</span></td>
+                                                            <td><a href="/sp/<%- cSpId %>/dashboard"><%- c.sp_name || '项目合同' %></a></td>
+                                                            <td><a href="<%= cDetailUrl %>"><%- cDisplayName %></a></td>
+                                                            <td><%- c.status_text %></td>
+                                                            <td><%- (c.begin_time ? ctx.moment(c.begin_time).format('YYYY/MM/DD HH:mm') : '') %></td>
+                                                            <td><a href="<%= cDetailUrl %>" class="btn btn-sm btn-table <%- cShowResubmitBtn ? 'btn-outline-warning text-warning' : 'btn-outline-primary' %>" role="button"><%- cShowResubmitBtn ? '重新上报' : '审批' %></a></td>
                                                         </tr>
                                                     <% } %>
                                                 <% } %>
@@ -1045,17 +1006,17 @@
                                             <span class="${acCostStage.auditStringClass[n.audit_status]}">${acCostStage.auditString[n.audit_status]}</span> <a href="/sp/${n.spid}/cost/tender/${n.id}/duty/1/stage" target="_blank">${n.name}</a> 责任成本 ${n.name}<span class="float-right">${moment(new Date(n.shenpi_time)).format('HH:mm:ss')}</span></li>`);
                                         break;
                                     case 'contract':
-                                        html.push(`<li class="list-group-item">
-                                            <span class="text-warning">审批中</span> <a href="/contract/${n.id}/audit" target="_blank">${n.name}</a> 合同审批<span class="float-right">${moment(new Date(n.shenpi_time)).format('HH:mm:ss')}</span></li>`);
-                                        break;
                                     case 'contractPay':
+                                    case 'contractReceipt': {
+                                        const cSpId = n.spid || n.tender_spid;
+                                        const cName = n.tid && n.tid !== 0 ? '标段合同-' + (n.contract_name || n.name) : '项目合同-' + (n.contract_name || n.name);
+                                        const cUrl = n.tid && n.tid !== 0
+                                            ? '/sp/' + cSpId + '/contract/tender/' + n.tid + '/detail' + (n.contract_type === 2 ? '/income' : '') + (n.shenpi_type !== 'contract' ? '?tab=pay' : '')
+                                            : '/sp/' + cSpId + '/contract/detail' + (n.contract_type === 2 ? '/income' : '') + (n.shenpi_type !== 'contract' ? '?tab=pay' : '');
                                         html.push(`<li class="list-group-item">
-                                            <span class="text-warning">审批中</span> <a href="/contract/${n.cid}/pay/${n.id}/audit" target="_blank">${n.contract_name || n.tender_name}</a> 合同支付 第${n.times}期<span class="float-right">${moment(new Date(n.shenpi_time)).format('HH:mm:ss')}</span></li>`);
-                                        break;
-                                    case 'contractReceipt':
-                                        html.push(`<li class="list-group-item">
-                                            <span class="text-warning">审批中</span> <a href="/contract/${n.cid}/pay/${n.id}/audit" target="_blank">${n.contract_name || n.tender_name}</a> 合同回款 第${n.times}期<span class="float-right">${moment(new Date(n.shenpi_time)).format('HH:mm:ss')}</span></li>`);
+                                            <span class="text-warning">审批中</span> <a href="${cUrl}" target="_blank">${cName}</a> 合同管理<span class="float-right">${moment(new Date(n.shenpi_time)).format('HH:mm:ss')}</span></li>`);
                                         break;
+                                    }
                                     default: continue;
                                 }
                             }

+ 5 - 5
app/view/sp_setting/user.ejs

@@ -2,10 +2,10 @@
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main">
-            <h2>账号管理
-                <a href="#sel-batch" data-toggle="modal" data-target="#sel-batch" class="btn btn-primary btn-sm pull-right mr-1">选择账号</a>
-                <a href="/setting/user" class="btn btn-primary btn-sm pull-right mr-1">新增账号</a>
-                <a href="#copy-user-batch" data-toggle="modal" data-target="#copy-user-batch" class="btn btn-primary btn-sm pull-right mr-1">设置账号至其他项目</a>
+            <h2>项目成员
+                <a href="#sel-batch" data-toggle="modal" data-target="#sel-batch" class="btn btn-primary btn-sm pull-right mr-1">选择项目成员</a>
+                <a href="/setting/user" class="btn btn-primary btn-sm pull-right mr-1">新增平台账号</a>
+                <a href="#copy-user-batch" data-toggle="modal" data-target="#copy-user-batch" class="btn btn-primary btn-sm pull-right mr-1">设置成员至其他项目</a>
             </h2>
         </div>
     </div>
@@ -13,7 +13,7 @@
         <div class="c-body">
             <div class="sjs-height-0">
                 <nav class="nav nav-tabs m-3" role="tablist">
-                    <a class="nav-item nav-link active" href="/sp/<%- ctx.subProject.id %>/setting/user" aria-selected="false">账号列表</a>
+                    <a class="nav-item nav-link active" href="/sp/<%- ctx.subProject.id %>/setting/user" aria-selected="false">成员列表</a>
                     <a class="nav-item nav-link"  href="/sp/<%- ctx.subProject.id %>/setting/user/permission?ptype=datacollect" aria-selected="true">模块权限</a>
                     <div class="ml-auto">
                         <form class="input-group input-group-sm" method="get">

+ 1 - 1
app/view/sp_setting/user_modal.ejs

@@ -2,7 +2,7 @@
     <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title">选择账号</h5>
+                <h5 class="modal-title">添加账号至项目成员</h5>
             </div>
             <div class="modal-body">
                 <div class="d-flex justify-content-between mb-2">

+ 2 - 2
config/menu.js

@@ -505,10 +505,10 @@ const projectSettingMenu = {
         caption: '标段自定义类别',
     },
     user: {
-        name: '账号设置',
+        name: '项目成员',
         display: true,
         url: '/setting/user',
-        caption: '账号设置',
+        caption: '项目成员',
     },
     fun: {
         name: '功能设置',

+ 4 - 0
sql/update.sql

@@ -35,6 +35,10 @@ ADD COLUMN `selected` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '报表用
 ALTER TABLE `zh_contract_tree`
 ADD COLUMN `selected` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '报表用,是否选择' AFTER `calc2`;
 
+ALTER TABLE `zh_contract_pay`
+ADD COLUMN `kp_price` decimal(30, 8) NULL DEFAULT 0 COMMENT '开票金额' AFTER `pay_type`,
+ADD COLUMN `kp_time` datetime NULL DEFAULT NULL COMMENT '开票时间' AFTER `kp_price`;
+
 ------------------------------------
 -- 表数据
 ------------------------------------