Jelajahi Sumber

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

Tony Kang 1 hari lalu
induk
melakukan
c2bfc3aa77
46 mengubah file dengan 1051 tambahan dan 574 penghapusan
  1. 31 29
      app/base/base_bills_service.js
  2. 22 4
      app/const/contract.js
  3. 2 2
      app/const/project_setting.js
  4. 22 10
      app/controller/change_controller.js
  5. 100 2
      app/controller/contract_controller.js
  6. 2 2
      app/controller/cost_controller.js
  7. 8 3
      app/controller/dashboard_controller.js
  8. 10 15
      app/controller/stage_controller.js
  9. 1 0
      app/controller/sub_proj_controller.js
  10. 6 0
      app/controller/tender_controller.js
  11. 16 10
      app/extend/helper.js
  12. 18 0
      app/lib/ledger.js
  13. 5 5
      app/public/js/change_information.js
  14. 80 55
      app/public/js/change_revise.js
  15. 67 26
      app/public/js/contract_detail.js
  16. 17 12
      app/public/js/gcl_gather.js
  17. 8 4
      app/public/js/material_exponent.js
  18. 2 1
      app/public/js/path_tree.js
  19. 27 0
      app/public/js/phase_pay_detail.js
  20. 17 12
      app/public/js/shares/gcl_gather_compare.js
  21. 66 0
      app/public/js/shares/import_material.js
  22. 25 0
      app/public/js/stage_pay.js
  23. 3 3
      app/public/js/sub_project.js
  24. 3 2
      app/router.js
  25. 35 18
      app/service/contract_col_set.js
  26. 4 0
      app/service/contract_pay.js
  27. 26 8
      app/service/contract_sp_audit.js
  28. 2 2
      app/service/cost_stage_detail.js
  29. 4 1
      app/service/stage.js
  30. 63 99
      app/service/stage_audit.js
  31. 32 23
      app/view/change/revise.ejs
  32. 18 0
      app/view/change/revise_modal.ejs
  33. 191 140
      app/view/contract/col_set.ejs
  34. 12 8
      app/view/contract/detail.ejs
  35. 8 0
      app/view/contract/detail_modal.ejs
  36. 10 9
      app/view/dashboard/index.ejs
  37. 21 60
      app/view/dashboard/workspace.ejs
  38. 1 0
      app/view/phase_pay/detail_modal.ejs
  39. 48 0
      app/view/shares/import_material_modal.ejs
  40. 5 5
      app/view/sp_setting/user.ejs
  41. 1 1
      app/view/sp_setting/user_modal.ejs
  42. 1 0
      app/view/stage/pay_modal.ejs
  43. 0 1
      app/view/sub_proj/modal.ejs
  44. 2 2
      config/menu.js
  45. 2 0
      config/web.js
  46. 7 0
      sql/update.sql

+ 31 - 29
app/base/base_bills_service.js

@@ -804,37 +804,39 @@ class BaseBillsSerivce extends TreeService {
                         newBills.ex_qty1 = this.ctx.helper.add(newBills.ex_qty1, newPos.ex_qty1);
                         if (defaultData) this.ctx.helper._.assignIn(newPos, defaultData);
 
-                        for (const ag of pos.ancGcl) {
-                            const nig = {
-                                id: this.uuid.v4(), tid: this.ctx.tender.id,
-                                add_user_id: userId, update_user_id: userId,
-                                lid: newPos.lid, pid: newPos.id, g_order: ag.g_order,
-                                is_aux: ag.is_aux || 0, name: ag.name || '', unit: ag.unit || '',
-                                drawing_code: ag.drawing_code || '', memo: ag.memo || '',
-                                quantity: ag.quantity || 0, expr: ag.expr || '', calc_template: ag.calc_template || ''
-                            };
-                            pasteAncGclData.push(nig);
-                            if (!ag.calc_template || ag.calcDetail.length === 0) continue;
-
-                            let sumQty = 0;
-                            for (const cd of ag.calcDetail) {
-                                const newDetail = {
-                                    id: this.uuid.v4(), tid: this.ctx.tender.id, lid: newPos.lid, pid: newPos.id, ag_id: nig.id,
-                                    create_user_id: userId, update_user_id: userId,
-                                    agd_order: cd.agd_order,
-                                    str1 : cd.str1 || '', str2 : cd.str1 || '', str3 : cd.str1 || '', str4 : cd.str1 || '',
-                                    num_a : cd.num_a || 0, num_b : cd.num_b || 0, num_c : cd.num_c || 0, num_d : cd.num_d || 0,
-                                    num_e : cd.num_e || 0, num_f : cd.num_f || 0, num_g : cd.num_g || 0, num_h : cd.num_h || 0,
-                                    num_i : cd.num_i || 0, num_j : cd.num_j || 0, num_k : cd.num_k || 0, num_l : cd.num_l || 0,
-                                    num_m : cd.num_m || 0, num_n : cd.num_n || 0, num_o : cd.num_o || 0, num_p : cd.num_p || 0,
-                                    num_q : cd.num_q || 0, num_r : cd.num_r || 0, num_s : cd.num_s || 0, num_t : cd.num_t || 0,
-                                    num_u : cd.num_u || 0,
-                                    qty: cd.qty || 0, expr: cd.expr || 0, spec: cd.spec || ''
+                        if(pos.ancGcl && pos.ancGcl instanceof Array) {
+                            for (const ag of pos.ancGcl) {
+                                const nig = {
+                                    id: this.uuid.v4(), tid: this.ctx.tender.id,
+                                    add_user_id: userId, update_user_id: userId,
+                                    lid: newPos.lid, pid: newPos.id, g_order: ag.g_order,
+                                    is_aux: ag.is_aux || 0, name: ag.name || '', unit: ag.unit || '',
+                                    drawing_code: ag.drawing_code || '', memo: ag.memo || '',
+                                    quantity: ag.quantity || 0, expr: ag.expr || '', calc_template: ag.calc_template || ''
                                 };
-                                sumQty = this.ctx.helper.add(sumQty, newDetail.qty);
-                                pasteAncGclDetailData.push(newDetail);
+                                pasteAncGclData.push(nig);
+                                if (!ag.calc_template || ag.calcDetail.length === 0) continue;
+
+                                let sumQty = 0;
+                                for (const cd of ag.calcDetail) {
+                                    const newDetail = {
+                                        id: this.uuid.v4(), tid: this.ctx.tender.id, lid: newPos.lid, pid: newPos.id, ag_id: nig.id,
+                                        create_user_id: userId, update_user_id: userId,
+                                        agd_order: cd.agd_order,
+                                        str1 : cd.str1 || '', str2 : cd.str1 || '', str3 : cd.str1 || '', str4 : cd.str1 || '',
+                                        num_a : cd.num_a || 0, num_b : cd.num_b || 0, num_c : cd.num_c || 0, num_d : cd.num_d || 0,
+                                        num_e : cd.num_e || 0, num_f : cd.num_f || 0, num_g : cd.num_g || 0, num_h : cd.num_h || 0,
+                                        num_i : cd.num_i || 0, num_j : cd.num_j || 0, num_k : cd.num_k || 0, num_l : cd.num_l || 0,
+                                        num_m : cd.num_m || 0, num_n : cd.num_n || 0, num_o : cd.num_o || 0, num_p : cd.num_p || 0,
+                                        num_q : cd.num_q || 0, num_r : cd.num_r || 0, num_s : cd.num_s || 0, num_t : cd.num_t || 0,
+                                        num_u : cd.num_u || 0,
+                                        qty: cd.qty || 0, expr: cd.expr || 0, spec: cd.spec || ''
+                                    };
+                                    sumQty = this.ctx.helper.add(sumQty, newDetail.qty);
+                                    pasteAncGclDetailData.push(newDetail);
+                                }
+                                nig.quantity = this.ctx.helper.round(sumQty, qtyDecimal);
                             }
-                            nig.quantity = this.ctx.helper.round(sumQty, qtyDecimal);
                         }
 
                         pastePosData.push(newPos);

+ 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,
 };

+ 2 - 2
app/const/project_setting.js

@@ -63,7 +63,7 @@ const colSet = {
         { field: 'budget_std', name: '概预算标准', fixed: ['show'] },
         { field: 'tender_count', name: '标段个数', fixed: [] },
         { field: 'file_count', name: '文件个数', fixed: [] },
-        { field: 'contract_count', name: '合同个数', fixed: [] },
+        { field: 'contract_count1', name: '合同个数', fixed: [] },
         { field: 'management', name: '管理单位', fixed: ['show'] },
         { field: 'create_time', name: '创建时间', fixed: [] },
         { field: 'control', name: '操作', fixed: ['show'] },
@@ -91,7 +91,7 @@ const defaultProjectColSet = {
         { field: 'budget_std', show: 1, alias: '' },
         { field: 'tender_count', show: 1, alias: '' },
         { field: 'file_count', show: 1, alias: '' },
-        { field: 'contract_count', show: 1, alias: '' },
+        { field: 'contract_count1', show: 1, alias: '' },
         { field: 'management', show: 1, alias: '' },
         { field: 'create_time', show: 1, alias: '' },
         { field: 'control', show: 1, alias: '' },

+ 22 - 10
app/controller/change_controller.js

@@ -444,12 +444,14 @@ module.exports = app => {
                         // 清单表页赋值
                         for (const au in change.userGroups) {
                             if (change.userGroups[au][0].audit_order !== 0) {
+                                // cl['audit_amount_' + change.userGroups[au][0].audit_order] =
+                                // (change.userGroups[au][0].audit_type === auditType.key.and &&
+                                //     ctx.helper._.findIndex(change.userGroups[au], { status: audit.change.status.checking }) !== -1 &&
+                                //     ctx.helper._.findIndex(change.userGroups[au], { uid: ctx.session.sessionUser.accountId, status: audit.change.status.checked }) !== -1) ||
+                                //     (change.shenpiPower && ctx.helper._.findIndex(change.userGroups[au], { uid: ctx.session.sessionUser.accountId }) !== -1) ?
+                                //     cl.spamount : (audit_amount[au - 1] !== undefined ? parseFloat(audit_amount[au - 1]) : null);
                                 cl['audit_amount_' + change.userGroups[au][0].audit_order] =
-                                (change.userGroups[au][0].audit_type === auditType.key.and &&
-                                    ctx.helper._.findIndex(change.userGroups[au], { status: audit.change.status.checking }) !== -1 &&
-                                    ctx.helper._.findIndex(change.userGroups[au], { uid: ctx.session.sessionUser.accountId, status: audit.change.status.checked }) !== -1) ||
-                                    (change.shenpiPower && ctx.helper._.findIndex(change.userGroups[au], { uid: ctx.session.sessionUser.accountId }) !== -1) ?
-                                    cl.spamount : (audit_amount[au - 1] !== undefined ? parseFloat(audit_amount[au - 1]) : null);
+                                    ctx.helper._.findIndex(change.userGroups[au], { status: audit.change.status.checking }) !== -1 ? cl.spamount : (audit_amount[au - 1] !== undefined ? parseFloat(audit_amount[au - 1]) : null);
                             }
                         }
                         if (change.readOnly && !change.shenpiPower) {
@@ -808,12 +810,14 @@ module.exports = app => {
                                 // 清单表页赋值
                                 for (const au in ctx.change.userGroups) {
                                     if (ctx.change.userGroups[au][0].audit_order !== 0) {
+                                        // cl['audit_amount_' + ctx.change.userGroups[au][0].audit_order] =
+                                        //     (ctx.change.userGroups[au][0].audit_type === auditType.key.and &&
+                                        //         ctx.helper._.findIndex(ctx.change.userGroups[au], { status: audit.change.status.checking }) !== -1 &&
+                                        //         ctx.helper._.findIndex(ctx.change.userGroups[au], { uid: ctx.session.sessionUser.accountId, status: audit.change.status.checked }) !== -1) ||
+                                        //     (ctx.change.shenpiPower && ctx.helper._.findIndex(ctx.change.userGroups[au], { uid: ctx.session.sessionUser.accountId }) !== -1) ?
+                                        //         cl.spamount : (audit_amount[au - 1] !== undefined ? parseFloat(audit_amount[au - 1]) : null);
                                         cl['audit_amount_' + ctx.change.userGroups[au][0].audit_order] =
-                                            (ctx.change.userGroups[au][0].audit_type === auditType.key.and &&
-                                                ctx.helper._.findIndex(ctx.change.userGroups[au], { status: audit.change.status.checking }) !== -1 &&
-                                                ctx.helper._.findIndex(ctx.change.userGroups[au], { uid: ctx.session.sessionUser.accountId, status: audit.change.status.checked }) !== -1) ||
-                                            (ctx.change.shenpiPower && ctx.helper._.findIndex(ctx.change.userGroups[au], { uid: ctx.session.sessionUser.accountId }) !== -1) ?
-                                                cl.spamount : (audit_amount[au - 1] !== undefined ? parseFloat(audit_amount[au - 1]) : null);
+                                            ctx.helper._.findIndex(ctx.change.userGroups[au], { status: audit.change.status.checking }) !== -1 ? cl.spamount : (audit_amount[au - 1] !== undefined ? parseFloat(audit_amount[au - 1]) : null);
                                     }
                                 }
                             }
@@ -1759,6 +1763,10 @@ module.exports = app => {
                     edit = false;
                     changing = true;
                 }
+                let ledgering = false;
+                if (ctx.tender.data.ledger_status !== audit.ledger.status.checked) {
+                    ledgering = true;
+                }
                 let revising = false;
                 // 判断是否在修订中,是则无法操作本页
                 const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
@@ -1797,6 +1805,7 @@ module.exports = app => {
                 renderData.readOnly = !edit;
                 renderData.changing = changing;
                 renderData.revising = revising;
+                renderData.ledgering = ledgering;
                 renderData.isYb = change.uid === ctx.session.sessionUser.accountId;
                 await this.layout('change/revise.ejs', renderData, 'change/revise_modal.ejs');
             } catch (err) {
@@ -1895,6 +1904,9 @@ module.exports = app => {
                 if (change.status !== audit.change.status.uncheck && change.status !== audit.change.status.checkNo && change.status !== audit.change.status.revise) {
                     throw '该变更令正在审批中或已完成,无法操作新增部位数据';
                 }
+                if (ctx.tender.data.ledger_status !== audit.ledger.status.checked) {
+                    throw '台账未审批通过,无法操作新增部位数据';
+                }
                 // 判断是否在修订中,是则无法操作本页
                 const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
                 if (lastRevise && lastRevise.status !== audit.revise.status.checked) {

+ 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 = {

+ 2 - 2
app/controller/cost_controller.js

@@ -421,9 +421,9 @@ module.exports = app => {
                             responseData.data[f] = detail;
                             break;
                         case 'detailCompare':
-                            const detailC = await ctx.service.costStageLedger.getReadData(ctx.costStage.relaStage);
+                            const detailC = await ctx.service.costStageDetail.getReadData(ctx.costStage.relaStage);
                             detailC.forEach(l => { delete l.postil; delete l.memo; });
-                            const bookDetailC = await ctx.service.costStageBook.getCompareData(ctx.costStage);
+                            const bookDetailC = await ctx.service.costStageBookDetail.getCompareData(ctx.costStage);
                             ctx.helper.assignRelaData(detailC, [
                                 { data: bookDetailC, fields: ['in_tp', 'in_excl_tax_tp', 'calc_his', 'postil', 'memo'], prefix: '', relaId: 'detail_id' },
                             ]);

+ 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;

+ 10 - 15
app/controller/stage_controller.js

@@ -429,13 +429,12 @@ module.exports = app => {
                                 responseData.data.ledgerData = await this._getStageLedgerData(ctx);
                             }
                             const assistLocked = await ctx.service.stageAuditAss.getLockedId(ctx.stage);
-                            const auditLocked = await ctx.service.stageAudit.getLockedId(ctx.stage);
-                            responseData.data.locked = [...assistLocked, ...auditLocked];
+                            const auditUnionLocked = await ctx.service.stageAudit.getUnionLockedId(ctx.stage);
+                            const auditLocked = await ctx.service.stageAudit.getAuditLockedId(ctx.stage);
+                            responseData.data.locked = [...assistLocked, ...auditUnionLocked, ...auditLocked];
                             if (ctx.tender.info.fun_rela.repel && !ctx.stage.readOnly) {
                                 const assistComfirm = await ctx.service.stageAuditAss.getComfirmLockedId(ctx.stage);
-                                const auditComfirm = ctx.stage.curAuditors && ctx.stage.curAuditors.length > 0 && ctx.stage.curAuditors[0].audit_type === auditType.key.union
-                                    ? await ctx.service.stageAuditAss.getComfirmLockedId(ctx.stage) : [];
-                                responseData.data.locked.push(...assistComfirm, ...auditComfirm);
+                                responseData.data.locked.push(...assistComfirm);
                             }
                             break;
                         case 'pos':
@@ -1492,8 +1491,8 @@ module.exports = app => {
                 }
 
                 await ctx.service.stageAudit.start(ctx.stage.id, ctx.stage.times);
-                const rstMsg = await ctx.service.roleRptRel.refreshSignatureDate(ctx.tender.data.id, ctx.stage.id, 'stage', -1, [ctx.stage.user_id]);
-                // console.log(rstMsg);
+                await ctx.service.roleRptRel.refreshSignatureDate(ctx.tender.data.id, ctx.stage.id, 'stage', -1, [ctx.stage.user_id]);
+                await ctx.service.stageAudit.checkAuditSkip(ctx.stage.id);
                 ctx.body = { err: 0, msg: '', data: [] };
             } catch (err) {
                 this.log(err);
@@ -1528,8 +1527,8 @@ module.exports = app => {
 
                 await ctx.service.stageAudit.check(ctx.stage.id, data, ctx.stage.times);
                 // // 这里要调用签名的service,重设签名日期
-                const rstMsg = await ctx.service.roleRptRel.refreshSignatureDate(ctx.tender.data.id, ctx.stage.id, 'stage', -1, [ctx.session.sessionUser.accountId]);
-                // console.log(rstMsg);
+                await ctx.service.roleRptRel.refreshSignatureDate(ctx.tender.data.id, ctx.stage.id, 'stage', -1, [ctx.session.sessionUser.accountId]);
+                await ctx.service.stageAudit.checkAuditSkip(ctx.stage.id);
                 ctx.body = { err: 0, msg: '', data: [] };
             } catch (err) {
                 this.log(err);
@@ -1569,9 +1568,8 @@ module.exports = app => {
 
                 if ((ctx.stage.finalAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0 || (ctx.query.confirm === '确认设置终审审批' && ctx.session.sessionUser.is_admin)) && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) {
                     await ctx.service.stageAudit.checkAgain(ctx.stage.id, ctx.stage.times, (ctx.query.confirm === '确认设置终审审批' && ctx.session.sessionUser.is_admin));
-                    // ctx.redirect(ctx.request.header.referer);
-                    const rstMsg = await ctx.service.roleRptRel.refreshSignatureDate(ctx.tender.data.id, ctx.stage.id, 'stage', -1, [ctx.session.sessionUser.accountId]);
-                    // console.log(rstMsg);
+                    await ctx.service.roleRptRel.refreshSignatureDate(ctx.tender.data.id, ctx.stage.id, 'stage', -1, [ctx.session.sessionUser.accountId]);
+                    await ctx.service.stageAudit.checkAuditSkip(ctx.stage.id);
                     ctx.body = {
                         err: 0,
                         url: ctx.request.header.referer,
@@ -1582,11 +1580,8 @@ module.exports = app => {
                 }
             } catch (err) {
                 this.log(err);
-                // ctx.session.postError = err.toString();
-                // ctx.redirect(ctx.request.header.referer);
                 ctx.body = {
                     err: 1,
-                    // url: ctx.request.header.referer,
                     msg: err,
                 };
             }

+ 1 - 0
app/controller/sub_proj_controller.js

@@ -21,6 +21,7 @@ module.exports = app => {
             for (const p of projectList) {
                 p.tender_count = await this.ctx.service.tender.count({ spid: p.id });
                 p.contract_count = await this.ctx.service.contract.count({ spid: p.id });
+                p.contract_count1 = await this.ctx.service.contract.count({ spid: p.id, contract_type: 1 });
                 p.file_count = await this.ctx.service.file.count({ spid: p.id, is_deleted: 0 });
             }
         }

+ 6 - 0
app/controller/tender_controller.js

@@ -1768,6 +1768,12 @@ module.exports = app => {
                         case 'tender_info':
                             responseData.data[f] = ctx.tender.info;
                             break;
+                        case 'materials':
+                            responseData.data[f] = await this.ctx.service.material.getAllDataByCondition({
+                                where: { tid: ctx.tender.id, status: auditConst.material.status.checked },
+                                orders: [['order', 'desc']],
+                            });
+                            break;
                         default:
                             throw '未知数据类型';
                     }

+ 16 - 10
app/extend/helper.js

@@ -451,21 +451,27 @@ module.exports = {
             return -1;
         }
         const numReg = /^[0-9]+$/;
+        const hasNumReg = /[0-9]+$/;
+        const splitNumReg = /\d+|[^\d]+/g;
         function compareSubCode(code1, code2) {
-            if (numReg.test(code1)) {
-                if (numReg.test(code2)) {
-                    return parseInt(code1) - parseInt(code2);
+            if (numReg.test(code1)) return numReg.test(code2) ? parseInt(code1) - parseInt(code2) : -1;
+            if (numReg.test(code2)) return 1;
+            if (hasNumReg.test(code1) && hasNumReg.test(code2)) {
+                const parts1 = code1.match(splitNumReg);
+                const parts2 = code2.match(splitNumReg);
+                for (let i = 0, iLen = Math.min(parts1.length, parts2.length); i < iLen; i++) {
+                    if (parts1[i] === parts2[i]) continue;
+                    if (numReg.test(parts1[i])) {
+                        return numReg.test(parts2[i]) ? parseInt(parts1[i]) - parseInt(parts2[i]) : -1;
+                    } else {
+                        return (parts1[i] < parts2[i] ? -1 : 1)
+                    }
                 }
-                return -1;
-
-            }
-            if (numReg.test(code2)) {
-                return 1;
+                return parts1.length - parts2.length;
             }
             return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); // code1.localeCompare(code2);
         }
-        const aCodes = str1.split(symbol),
-            bCodes = str2.split(symbol);
+        const aCodes = str1.split(symbol), bCodes = str2.split(symbol);
         for (let i = 0, iLength = Math.min(aCodes.length, bCodes.length); i < iLength; ++i) {
             const iCompare = compareSubCode(aCodes[i], bCodes[i]);
             if (iCompare !== 0) {

+ 18 - 0
app/lib/ledger.js

@@ -429,6 +429,24 @@ class billsTree extends baseTree {
         }
         return null;
     }
+    checkUsed(node, fields) {
+        if (!node) return false;
+        const checkNodes = node.children && node.children.length > 0 ? this.getPosterity(node) : [node];
+        for (const cn of checkNodes) {
+            if (cn.children && cn.children.length > 0) continue;
+            for (const f of fields) {
+                if (cn[f]) return true;
+            }
+        }
+        return false;
+    }
+    checkNodesUsed(nodes, fields) {
+        if (!nodes || nodes.length === 0) return false;
+        for (const n of nodes) {
+            if (this.checkUsed(n, fields)) return true;
+        }
+        return false;
+    }
 }
 
 class filterTree extends baseTree {

+ 5 - 5
app/public/js/change_information.js

@@ -678,11 +678,11 @@ $(document).ready(() => {
                 return ZhCalc.round(data.camount, findDecimal(data.unit));
             },
             changed_amount: function (data) {
-                return ZhCalc.round(ZhCalc.add(data.oamount2, data.camount), findDecimal(data.unit));
+                return ZhCalc.round(ZhCalc.add(data.oamount2, data.spamount), findDecimal(data.unit));
             },
             changed_tp: function (data) {
-                return ZhCalc.add(ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.oamount2, findDecimal(data.unit))), totalPriceUnit),
-                    ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.camount, findDecimal(data.unit))), totalPriceUnit));
+                const otp = ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.oamount2, findDecimal(data.unit))), totalPriceUnit);
+                return ZhCalc.add(otp, ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.spamount, findDecimal(data.unit))), totalPriceUnit));
             },
             // del_list: function (data) {
             //     return !_.find(changeUsedData, { cbid: data.id }) && !checkIsSettle(data) ? '移除' : '';
@@ -811,7 +811,7 @@ $(document).ready(() => {
                     }
                     c.order = node.order;
                 }
-                if (!_.isEqual(node.oamount2, oamount2) || !_.isEqual(node.camount, camount) || !_.isEqual(node.samount, samount)) {
+                if (!_.isEqual(node.oamount2, oamount2) || !_.isEqual(node.camount, camount) || !_.isEqual(node.samount, samount) || !_.isEqual(node.spamount, spamount)) {
                     node.oamount2 = oamount2;
                     node.camount = camount;
                     node.samount = samount;
@@ -2995,7 +2995,7 @@ $(document).ready(() => {
                     spamount = ZhCalc.add(spamount, ZhCalc.round(np.spamount, findDecimal(node.unit)));
                     if (np.is_valuation === 0) is_valuation = 0;
                 }
-                if (!_.isEqual(node.oamount2, oamount2) || !_.isEqual(node.camount, camount) || !_.isEqual(node.samount, samount)) {
+                if (!_.isEqual(node.oamount2, oamount2) || !_.isEqual(node.camount, camount) || !_.isEqual(node.samount, samount) || !_.isEqual(node.spamount, spamount)) {
                     node.oamount2 = oamount2;
                     node.camount = camount;
                     node.samount = samount;

+ 80 - 55
app/public/js/change_revise.js

@@ -99,7 +99,7 @@ $(document).ready(() => {
         },
         readOnly: {
             isChangeAdd: function (data) {
-                return !readOnly && !data.formc;
+                return ledgering || (!readOnly && !data.formc);
             },
             isChangeList: function (data) {
                 if (!data) return true;
@@ -146,7 +146,7 @@ $(document).ready(() => {
         },
         readOnly: {
             isChangeAdd: function (data) {
-                return !readOnly && !data.formc && (!data.settle_status || (data.settle_status && data.settle_status === settleStatus.finish));
+                return ledgering || (!readOnly && !data.formc && (!data.settle_status || (data.settle_status && data.settle_status === settleStatus.finish)));
             },
             isChangeList: function (data) {
                 if (!data) return true;
@@ -382,16 +382,16 @@ $(document).ready(() => {
                     }
 
                     if (col.field.indexOf('dgn') >= 0) {
-                        $('#bills-expr').attr('readOnly', readOnly || cell.locked() || (_.isString(data.b_code) && data.b_code !== '') || !data.formc);
+                        $('#bills-expr').attr('readOnly', ledgering || readOnly || cell.locked() || (_.isString(data.b_code) && data.b_code !== '') || !data.formc);
                     } else if (col.field === 'unit_price') {
-                        $('#bills-expr').attr('readOnly', readOnly || cell.locked() || (data.children && data.children.length > 0) || (_.isBoolean(data.used) && data.used === true) || !data.formc);
+                        $('#bills-expr').attr('readOnly', ledgering || readOnly || cell.locked() || (data.children && data.children.length > 0) || (_.isBoolean(data.used) && data.used === true) || !data.formc);
                     } else {
                         const nodePos = pos.getLedgerPos(data.id);
                         if (nodePos && nodePos.length > 0) {
                             $('#bills-expr').val('').attr('readOnly', true);
                             $('#bills-expr').removeAttr('data-row');
                         } else {
-                            $('#bills-expr').attr('readOnly', readOnly || cell.locked() || (data.children && data.children.length > 0) || !data.formc);
+                            $('#bills-expr').attr('readOnly', ledgering || readOnly || cell.locked() || (data.children && data.children.length > 0) || !data.formc);
                         }
                     }
                     $('#bills-expr').attr('data-row', sel.row);
@@ -577,6 +577,7 @@ $(document).ready(() => {
          * @param spread
          */
         baseOpr: function (sheet, type, addCount = 1) {
+            if (ledgering) return;
             const self = this;
             const [tree, node, count] = this.getDefaultSelectInfo(sheet);
             if (!tree || !node || !count) return;
@@ -870,6 +871,10 @@ $(document).ready(() => {
                 if (col.field === 'is_change' || col.field === 'is_valuation' || col.field === 'is_new_price') {
                     return;
                 }
+                if (ledgering && col.field !== 'camount') {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
                 const sortData = info.sheet.zh_dataType === 'tree' ? info.sheet.zh_tree.nodes : info.sheet.zh_data;
                 const node = sortData[info.row];
                 if (col.field === 'camount') {
@@ -1185,6 +1190,7 @@ $(document).ready(() => {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
+                    if (ledgering && colSetting.field !== 'camount') continue;
                     const value = colSetting.wordWrap ? pasteData[iRow-filterRow][iCol] : trimInvalidChar(pasteData[iRow-filterRow][iCol]);
                     if (node.children && node.children.length > 0 && invalidFields.parent.indexOf(colSetting.field) >= 0) {
                         toastMessageUniq(hint.parent);
@@ -1561,6 +1567,7 @@ $(document).ready(() => {
                     const col = sheet.zh_setting.cols[iCol];
                     const style = sheet.getStyle(iRow, iCol);
                     if (style.locked || (['dgn_qty1', 'dgn_qty2'].indexOf(col.field) >= 0 && node.b_code)) continue;
+                    if (ledgering && col.field !== 'camount') continue;
                     if (col.type === 'Number' && node.settle_status === settleStatus.finish) continue;
 
                     if (['dgn_qty1', 'dgn_qty2', 'camount'].indexOf(col.field) < 0 && sheet.zh_tree.checkNodeUsed(node, pos)) {
@@ -1637,6 +1644,7 @@ $(document).ready(() => {
             }
         },
         pasteBlock: function (spread, copyInfo) {
+            if (ledgering) return;
             const self = this;
             const sheet = spread.getActiveSheet();
             const [tree, node] = this.getDefaultSelectInfo(spread.getActiveSheet());
@@ -1779,6 +1787,7 @@ $(document).ready(() => {
             }
         },
         sortCode: function (sheet) {
+            if (ledgering) return;
             const tree = sheet.zh_tree;
             if (!tree) return;
 
@@ -1963,15 +1972,17 @@ $(document).ready(() => {
     billsSpread.bind(spreadNS.Events.topRowChanged, billsTreeSpreadObj.topRowChanged);
     if (!readOnly) {
         // 增删上下移升降级
-        $('a[name="base-opr"]').click(function () {
-            billsTreeSpreadObj.baseOpr(billsSheet, this.getAttribute('type'));
-        });
-        $('a[name=cpc]').click(function () {
-            billsSpread.commandManager().execute({
-                cmd: this.getAttribute('type'),
-                sheetName: billsSheet.name()
+        if (!ledgering) {
+            $('a[name="base-opr"]').click(function () {
+                billsTreeSpreadObj.baseOpr(billsSheet, this.getAttribute('type'));
             });
-        });
+            $('a[name=cpc]').click(function () {
+                billsSpread.commandManager().execute({
+                    cmd: this.getAttribute('type'),
+                    sheetName: billsSheet.name()
+                });
+            });
+        }
 
         $('#bills-expr').bind('change onblur', function () {
             if (this.readOnly) return;
@@ -2143,7 +2154,7 @@ $(document).ready(() => {
             SpreadJsObj.Clipboard.setCopyData(copyText);
         });
         SpreadJsObj.addDeleteBind(billsSpread, billsTreeSpreadObj.deletePress);
-        SpreadJsObj.addCutEvents(billsSpread, billsTreeSpreadObj.cut);
+        if (!ledgering) SpreadJsObj.addCutEvents(billsSpread, billsTreeSpreadObj.cut);
     }
     // 右键菜单
     let batchInsertObj;
@@ -2181,7 +2192,7 @@ $(document).ready(() => {
         },
         items: {}
     };
-    if (!readOnly) {
+    if (!readOnly && !ledgering) {
         billsContextMenuOptions.items.create = {
             name: '新增',
             icon: 'fa-sign-in',
@@ -2327,7 +2338,7 @@ $(document).ready(() => {
             return select && select.level <= 1;
         }
     };
-    if (!readOnly) {
+    if (!readOnly && !ledgering) {
         billsContextMenuOptions.items.pasteBlock = {
             name: '粘贴整块',
             icon: 'fa-clipboard',
@@ -2351,7 +2362,7 @@ $(document).ready(() => {
         };
         billsContextMenuOptions.items.sprBlock = '----';
     }
-    if (!readOnly) {
+    if (!readOnly && !ledgering) {
         billsContextMenuOptions.items.batchInsert = {
             name: '批量插入',
             type: 'batchInsert',
@@ -2498,7 +2509,7 @@ $(document).ready(() => {
                         ? (data[exprInfo.expr] ? data[exprInfo.expr] : data[col.field])
                         : data[col.field];
                     $('#pos-expr').val(value).attr('field', col.field).attr('org', data[col.field])
-                        .attr('readOnly', readOnly || !data.formc || data.settle_status === settleStatus.finish).attr('data-row', sel.row);
+                        .attr('readOnly', ledgering || readOnly || !data.formc || data.settle_status === settleStatus.finish).attr('data-row', sel.row);
                 } else {
                     $('#pos-expr').val('').attr('readOnly', true);
                     $('#pos-expr').removeAttr('data-row');
@@ -2529,7 +2540,7 @@ $(document).ready(() => {
                     posSheet.zh_setting.cols.forEach(function (col, i) {
                         for (let iRow = 0; iRow < posSheet.getRowCount(); iRow++) {
                             if (i !== 0 && col.field !== 'camount' && col.field !== 'is_valuation') {
-                                posSheet.getCell(iRow, i).locked((posSheet.zh_data[iRow] && !posSheet.zh_data[iRow].formc) || posSheet.zh_setting.readOnly || false);
+                                posSheet.getCell(iRow, i).locked(ledgering || (posSheet.zh_data[iRow] && !posSheet.zh_data[iRow].formc) || posSheet.zh_setting.readOnly || false);
                             } else {
                                 let lock = false;
                                 if (!posSheet.zh_data[iRow]) {
@@ -2549,6 +2560,7 @@ $(document).ready(() => {
             posSpreadObj.refreshOperationValid(posSheet);
         },
         baseOpr: function (sheet, type) {
+            if (ledgering) return;
             const data = {
                 postType: 'pos',
                 posPostType: type === 'delete' ? type : 'update',
@@ -2800,6 +2812,10 @@ $(document).ready(() => {
             if (col.field === 'is_change' || col.field === 'is_valuation') {
                 return;
             }
+            if (ledgering && col.field !== 'camount') {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                return;
+            }
             if (col.field === 'camount') {
                 if (!node) {
                     toastr.error('数据错误,请选择台账节点后再试');
@@ -3107,6 +3123,7 @@ $(document).ready(() => {
                     const cData = {};
                     for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
                         const colSetting = sheet.zh_setting.cols[iCol];
+                        if (ledgering && colSetting.field !== 'camount') continue;
                         if (colSetting.field === 'name') {
                             toastr.error('部位名称不能为空');
                             return;
@@ -3210,6 +3227,7 @@ $(document).ready(() => {
          * @param sheet
          */
         deletePos: function (sheet) {
+            if (ledgering) return;
             const selection = sheet.getSelections();
             const data = {
                 postType: 'pos',
@@ -3313,13 +3331,14 @@ $(document).ready(() => {
             const lastOrder = sortData.length > 0 ? sortData[sortData.length - 1].porder + 1 : 1;
             makeGclGatherData();
             for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
-                let bPaste = true;
+                let bPaste = !ledgering;
                 const curRow = info.cellRange.row + iRow;
                 const posData = curRow >= sortData.length ? {lid: node.id, porder: lastOrder + curRow - sortData.length} : {id: sortData[curRow].id, lid: node.id};
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
                     if (!colSetting) continue;
+                    if (ledgering && colSetting.field !== 'camount') continue;
 
                     posData[colSetting.field] = colSetting.wordWrap ? info.sheet.getText(curRow, curCol) : trimInvalidChar(info.sheet.getText(curRow, curCol));
                     if (posData.id && colSetting.type === 'Number' && sortData[curRow].settle_status === settleStatus.finish) {
@@ -3504,6 +3523,7 @@ $(document).ready(() => {
             posSpreadObj.refreshOperationValid(posSheet);
         },
         addPegs: function (pegs) {
+            if (ledgering) return;
             if (!pegs || pegs.length <= 0) return;
             const node = SpreadJsObj.getSelectObject(billsSheet);
             if (!node) return;
@@ -3524,9 +3544,11 @@ $(document).ready(() => {
     };
     posSpread.bind(spreadNS.Events.SelectionChanged, posSpreadObj.selectionChanged);
     if (!readOnly) {
-        $('a[name="pos-opr"]').click(function () {
-            posSpreadObj.baseOpr(posSheet, this.getAttribute('type'));
-        });
+        if (!ledgering) {
+            $('a[name="pos-opr"]').click(function () {
+                posSpreadObj.baseOpr(posSheet, this.getAttribute('type'));
+            });
+        }
         $('#pos-expr').bind('change onblur', function () {
             if (this.readOnly) return;
 
@@ -3688,39 +3710,41 @@ $(document).ready(() => {
         posSpread.bind(spreadNS.Events.ClipboardPasted, posSpreadObj.clipboardPasted);
         SpreadJsObj.addDeleteBind(posSpread, posSpreadObj.deletePress);
 
-        $.contextMenu({
-            selector: '#pos-spread',
-            build: function ($trigger, e) {
-                const target = SpreadJsObj.safeRightClickSelection($trigger, e, posSpread);
-                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
-            },
-            items: {
-                'delete': {
-                    name: '删除',
-                    icon: 'fa-remove',
-                    disabled: function (key, opt) {
-                        if (posSheet.zh_data) {
-                            const selection = posSheet.getSelections();
-                            const sel = selection ? selection[0] : sheet.getSelections()[0];
-                            const row = sel ? sel.row : -1;
-                            const rowCount = sel.rowCount;
-                            let settleFinish = false;
-                            for (let i = row; i < row + rowCount; i++) {
-                                if (!posSheet.zh_data[i]) continue;
-                                settleFinish = settleFinish || posSheet.zh_data[i].settle_status === settleStatus.finish;
+        if (!ledgering) {
+            $.contextMenu({
+                selector: '#pos-spread',
+                build: function ($trigger, e) {
+                    const target = SpreadJsObj.safeRightClickSelection($trigger, e, posSpread);
+                    return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+                },
+                items: {
+                    'delete': {
+                        name: '删除',
+                        icon: 'fa-remove',
+                        disabled: function (key, opt) {
+                            if (posSheet.zh_data) {
+                                const selection = posSheet.getSelections();
+                                const sel = selection ? selection[0] : sheet.getSelections()[0];
+                                const row = sel ? sel.row : -1;
+                                const rowCount = sel.rowCount;
+                                let settleFinish = false;
+                                for (let i = row; i < row + rowCount; i++) {
+                                    if (!posSheet.zh_data[i]) continue;
+                                    settleFinish = settleFinish || posSheet.zh_data[i].settle_status === settleStatus.finish;
+                                }
+                                return (posSheet.zh_data.length < selection[0].row + selection[0].rowCount) || (posSheet.zh_data[selection[0].row] && !posSheet.zh_data[selection[0].row].formc) || settleFinish;
+                            } else {
+                                return true;
                             }
-                            return (posSheet.zh_data.length < selection[0].row + selection[0].rowCount) || (posSheet.zh_data[selection[0].row] && !posSheet.zh_data[selection[0].row].formc) || settleFinish;
-                        } else {
-                            return true;
+                        },
+                        callback: function (key, opt) {
+                            posSpreadObj.deletePos(posSheet);
                         }
                     },
-                    callback: function (key, opt) {
-                        posSpreadObj.deletePos(posSheet);
-                    }
-                },
-            }
+                }
 
-        });
+            });
+        }
     }
 
     // 加载清单&计量单元数据
@@ -3805,7 +3829,7 @@ $(document).ready(() => {
             this.spreadSetting = spreadSetting;
             this.spread = SpreadJsObj.createNewSpread(this.obj);
             SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
-            if (!readOnly) {
+            if (!readOnly && !ledgering) {
                 this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
                     const dealSheet = info.sheet;
                     const mainSheet = billsSheet;
@@ -4218,7 +4242,7 @@ $(document).ready(() => {
             this.spread = SpreadJsObj.createNewSpread($(`#cb-${spreadSetting.stdType}-spread`)[0]);
             const searchSheet = this.spread.getActiveSheet();
             SpreadJsObj.initSheet(searchSheet, this.spreadSetting);
-            if (!readOnly) {
+            if (!readOnly && !ledgering) {
                 this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
                     const dealSheet = info.sheet;
                     const mainSheet = billsSheet;
@@ -4443,7 +4467,7 @@ $(document).ready(() => {
                     }, 100);
                 });
             })(`a[name=${relaSelect.showLevel}]`, searchSheet);
-            if (!readOnly) {
+            if (!readOnly && !ledgering) {
                 this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
                     const dealSheet = info.sheet;
                     const mainSheet = billsSheet;
@@ -4781,6 +4805,7 @@ $(document).ready(() => {
     if (posSpread) posSpread.refresh();
 
     const stdLibCellDoubleClick = function (updateData, stdNode, stdTree) {
+        if (ledgering) return;
         const mainSheet = billsSheet;
         if (!stdTree || !mainSheet.zh_tree) { return; }
 

+ 67 - 26
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,12 +696,14 @@ $(document).ready(function() {
                                         <td>${pay.yf_price}</td>
                                         <td>${pay.sf_price}</td>
                                         <td>${pay.pay_type}</td>
+                                        <td>${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>
                                         <td><a href="javascript:void(0);" class="text-primary open-pay-files" data-cpid="${pay.id}"><i class="fa fa-paperclip fa-rotate-90"></i></a> <span class="files-num">${pay.files.length > 0 ? pay.files.length : ''}</span></td>
                                         ${shenpi_html}
-                                        <td>${operationHtml}</td>
+                                        <td class="contract-pay-operation-cell">${operationHtml}</td>
                                     </tr>`;
             });
             $('#htpay-table tbody').html(paysHtml);
@@ -2231,6 +2256,7 @@ $(document).ready(function() {
     });
 
     const payTime = $('#cons-addpay input[name="pay_time"]').datepicker().data('datepicker');
+    const kpTime = $('#cons-addpay input[name="kp_time"]').datepicker().data('datepicker');
     $('.datepicker-here').datepicker({
         autoClose: true,
     });
@@ -2371,6 +2397,9 @@ $(document).ready(function() {
         $('#cons-addpay input[name="yf_price"]').val(cpInfo ? cpInfo.yf_price : '');
         $('#cons-addpay input[name="sf_price"]').val(cpInfo ? cpInfo.sf_price : '');
         $('#cons-addpay select[name="pay_type"]').val(cpInfo ? cpInfo.pay_type : $('#cons-addpay select[name="pay_type"] option:first').text());
+        $('#cons-addpay input[name="kp_price"]').val(cpInfo ? cpInfo.kp_price : '');
+        $('#cons-addpay input[name="kp_time"]').val(cpInfo && cpInfo.kp_time ? moment(cpInfo.kp_time).format('YYYY-MM-DD') : '');
+        kpTime.selectDate(cpInfo && cpInfo.kp_time ? new Date(cpInfo.kp_time) : '');
         $('#cons-addpay textarea[name="remark"]').val(cpInfo ? cpInfo.remark : '');
         $('#cons-addpay .yf-tips').text('');
         $('#cons-addpay .sf-tips').text('');
@@ -2381,6 +2410,8 @@ $(document).ready(function() {
             $('#cons-addpay input[name="pay_price"]').attr('readonly', true);
             $('#cons-addpay input[name="debit_price"]').attr('readonly', true);
             $('#cons-addpay select[name="pay_type"]').attr('disabled', true);
+            $('#cons-addpay input[name="kp_price"]').attr('readonly', true);
+            $('#cons-addpay input[name="kp_time"]').attr('readonly', true);
             $('#cons-addpay textarea[name="remark"]').attr('readonly', true);
         } else {
             $('#cons-addpay input[name="pay_time"]').removeAttr('readonly');
@@ -2388,6 +2419,8 @@ $(document).ready(function() {
             $('#cons-addpay input[name="pay_price"]').removeAttr('readonly');
             $('#cons-addpay input[name="debit_price"]').removeAttr('readonly');
             $('#cons-addpay select[name="pay_type"]').removeAttr('readonly');
+            $('#cons-addpay input[name="kp_price"]').removeAttr('readonly');
+            $('#cons-addpay input[name="kp_time"]').removeAttr('readonly');
             $('#cons-addpay textarea[name="remark"]').removeAttr('readonly');
         }
     }
@@ -2728,15 +2761,15 @@ $(document).ready(function() {
             '            <div class="dropdown-menu dropdown-menu-right p-2" aria-labelledby="contractSearchFilterBtn" style="width: 260px;">\n' +
             '                <div class="form-check form-check-inline mb-1">\n' +
             '                    <input class="form-check-input" type="checkbox" id="contractSearchSignDate">\n' +
-            '                    <label class="form-check-label small" for="contractSearchSignDate">签订日期超过</label>\n' +
+            '                    <label class="form-check-label" for="contractSearchSignDate">签订日期超过</label>\n' +
             '                    <input type="number" class="form-control form-control-sm mx-1" id="contractSearchSignDays" value="180" min="0" step="1" style="width: 70px;">\n' +
-            '                    <label class="form-check-label small">天</label>\n' +
+            '                    <label class="form-check-label">天</label>\n' +
             '                </div>\n' +
             '                <div class="form-check form-check-inline mb-1">\n' +
             '                    <input class="form-check-input" type="checkbox" id="contractSearchSfRatio">\n' +
-            '                    <label class="form-check-label small" for="contractSearchSfRatio">实付比例小于</label>\n' +
+            '                    <label class="form-check-label" for="contractSearchSfRatio">实付比例小于</label>\n' +
             '                    <input type="number" class="form-control form-control-sm mx-1" id="contractSearchSfPercent" value="50" min="0" max="100" step="1" style="width: 70px;">\n' +
-            '                    <label class="form-check-label small">%</label>\n' +
+            '                    <label class="form-check-label">%</label>\n' +
             '                </div>\n' +
             '            </div>\n' +
             '            <span class="input-group-text" id="contractSearchCount">结果:0</span>\n' +
@@ -2832,6 +2865,9 @@ $(document).ready(function() {
             const cur = data[info.row];
             if (!cur || !cur.contract_id) return;
             SpreadJsObj.locateTreeNode(contractSheet, cur.contract_id, true);
+            contractTreeSpreadObj.refreshOperationValid(contractSheet);
+            contractTreeSpreadObj.setContract(contractSheet);
+            SpreadJsObj.saveTopAndSelect(contractSheet, ckBillsSpread);
         });
 
         return {spread: resultSpread};
@@ -3315,6 +3351,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() || 0,
+            kp_time: $('#cons-addpay input[name="kp_time"]').val() || null,
             remark: $('#cons-addpay textarea[name="remark"]').val(),
         }
         const name = contract_type === contractConst.type.expenses ? '付' : '回';
@@ -3370,6 +3408,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() || 0,
+            kp_time: $('#cons-addpay input[name="kp_time"]').val() || null,
             remark: $('#cons-addpay textarea[name="remark"]').val(),
         }
         const name = contract_type === contractConst.type.expenses ? '付' : '回';
@@ -4387,11 +4427,13 @@ $(document).ready(function() {
         });
     })('a[name=showLevel]', contractSheet);
 
+    let contractSearch;
     $.divResizer({
         select: '#right-spr',
         callback: function () {
             contractSpread.refresh();
             if (stdXmj) stdXmj.spread.refresh();
+            if (contractSearch) contractSearch.spread.refresh();
             const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
         }
     });
@@ -4407,7 +4449,6 @@ $(document).ready(function() {
         }
     });
     // 展开收起月信息价并浏览器记住本期展开收起
-    let contractSearch;
     $('a', '.right-nav').bind('click', function () {
         const tab = $(this), tabPanel = $(tab.attr('content'));
         if (!tab.hasClass('active')) {

+ 17 - 12
app/public/js/gcl_gather.js

@@ -498,22 +498,27 @@ const gclGatherModel = (function () {
             return -1;
         }
 
+        const numReg = /^[0-9]+$/;
+        const hasNumReg = /[0-9]+$/;
+        const splitNumReg = /\d+|[^\d]+/g;
         function compareSubCode(code1, code2) {
-            if (numReg.test(code1)) {
-                if (numReg.test(code2)) {
-                    return parseInt(code1) - parseInt(code2);
-                } else {
-                    return -1
-                }
-            } else {
-                if (numReg.test(code2)) {
-                    return 1;
-                } else {
-                    return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); //code1.localeCompare(code2);
+            if (numReg.test(code1)) return numReg.test(code2) ? parseInt(code1) - parseInt(code2) : -1;
+            if (numReg.test(code2)) return 1;
+            if (hasNumReg.test(code1) && hasNumReg.test(code2)) {
+                const parts1 = code1.match(splitNumReg);
+                const parts2 = code2.match(splitNumReg);
+                for (let i = 0, iLen = Math.min(parts1.length, parts2.length); i < iLen; i++) {
+                    if (parts1[i] === parts2[i]) continue;
+                    if (numReg.test(parts1[i])) {
+                        return numReg.test(parts2[i]) ? parseInt(parts1[i]) - parseInt(parts2[i]) : -1;
+                    } else {
+                        return (parts1[i] < parts2[i] ? -1 : 1)
+                    }
                 }
+                return parts1.length - parts2.length;
             }
+            return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); //code1.localeCompare(code2);
         }
-        const numReg = /^[0-9]+$/;
         const aCodes = str1.split(symbol), bCodes = str2.split(symbol);
         for (let i = 0, iLength = Math.min(aCodes.length, bCodes.length); i < iLength; ++i) {
             const iCompare = compareSubCode(aCodes[i], bCodes[i]);

+ 8 - 4
app/public/js/material_exponent.js

@@ -310,7 +310,8 @@ $(document).ready(() => {
                         SpreadJsObj.reLoadRowData(info.sheet, info.row);
                         return;
                     } else if (validText !== null) {
-                        const reg = new RegExp("^(\\d{1,10}\\.\\d{1,"+ materialExponentDecimal.up +"})?$");
+                        const up = Number(materialExponentDecimal.up);
+                        const reg = new RegExp(up > 0 ? `^(\\d{1,10}\\.\\d{1,${up}})?$` : `^(\\d{1,10})?$`);
                         if(!Number.isInteger(num) && !reg.test(num)) {
                             toastr.warning('已保留' + materialExponentDecimal.up + '位小数');
                         } else if (validText.toString().length > 11) {
@@ -328,7 +329,8 @@ $(document).ready(() => {
                         return;
                     }
                     let num = parseFloat(validText);
-                    const reg = new RegExp("^\\d+(\\.\\d{1,"+ materialExponentDecimal.qty +"})?$");
+                    const qty = Number(materialExponentDecimal.qty);
+                    const reg = new RegExp(qty > 0 ? `^\\d+(\\.\\d{1,${qty}})?$` : `^\\d+$`);
                     if (validText !== null && (num < 0 || num >= 1)) {
                         toastr.error('请输入0~1范围内并且小于' + materialExponentDecimal.qty +'位小数的浮点数');
                         SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -526,7 +528,8 @@ $(document).ready(() => {
                             bPaste = false;
                             continue;
                         } else if (validText !== null) {
-                            const reg = new RegExp("^(\\d{1,10}\\.\\d{1,"+ materialExponentDecimal.up +"})?$");
+                            const up = Number(materialExponentDecimal.up);
+                            const reg = new RegExp(up > 0 ? `^(\\d{1,10}\\.\\d{1,${up}})?$` : `^(\\d{1,10})?$`);
                             if(!Number.isInteger(num) && !reg.test(num)) {
                                 toastMessageUniq(getPasteHint(hint.numberCanUp, hintRow));
                             } else if (validText.toString().length > 11) {
@@ -544,7 +547,8 @@ $(document).ready(() => {
                             continue;
                         }
                         let num = parseFloat(validText);
-                        const reg = new RegExp("^\\d+(\\.\\d{1,"+ materialExponentDecimal.qty +"})?$");
+                        const qty = Number(materialExponentDecimal.qty);
+                        const reg = new RegExp(qty > 0 ? `^\\d+(?:\\.\\d{1,${qty}})?$` : `^\\d+$`);
                         if (validText !== null && (num < 0 || num >= 1)) {
                             toastMessageUniq(getPasteHint(hint.numberCan2, hintRow));
                             bPaste = false;

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

@@ -1443,7 +1443,8 @@ const createNewPathTree = function (type, setting) {
         _loadLockedNodes(ids){
             this.Locked = ids || [];
             for (const f of this.Locked) {
-                f.locked_ledger_id = f.ass_ledger_id ? f.ass_ledger_id.split(',') : (f.audit_ledger_id ? f.audit_ledger_id.split(',') : []);
+                const lockedId = f.ass_ledger_id || f.audit_locked_lid || f.audit_ledger_id;
+                f.locked_ledger_id = lockedId ? lockedId.split(',') : [];
                 for (const id of f.locked_ledger_id) {
                     const node = this.getItems(id);
                     if (!node) continue;

+ 27 - 0
app/public/js/phase_pay_detail.js

@@ -951,6 +951,19 @@ $(document).ready(() => {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
                 });
             },
+            importMaterial: function(node, tp, memo) {
+                const data = { postType: 'update', postData: { id: node.id, tp, postil: memo } };
+                postData('update', data, function (result) {
+                    if (result.reload) {
+                        payEvent.reloadPays(result.reload);
+                    } else {
+                        const refreshData = payTree.loadPostData(result);
+                        payEvent.refreshTree(refreshData);
+                    }
+                }, function () {
+                    SpreadJsObj.reLoadRowData(sheet, payTree.getNodeIndex(node));
+                });
+            }
         };
         spread.bind(spreadNS.Events.SelectionChanged, payEvent.selectionChanged);
         if (!readOnly) {
@@ -1172,6 +1185,20 @@ $(document).ready(() => {
                             return (!select.children || select.children.length === 0) && !readOnly && payUtils.menuVisible.deadline(select);
                         }
                     },
+                    'importMaterial': {
+                        name: '导入材差数据',
+                        visible: function(key, opt) {
+                            const select = SpreadJsObj.getSelectObject(sheet);
+                            return (!select.children || select.children.length ===0) && !readOnly;
+                        },
+                        callback: function(key, opt) {
+                            const select = SpreadJsObj.getSelectObject(sheet);
+                            importMaterial.show({
+                                relaNode: select,
+                                callback: payEvent.importMaterial,
+                            })
+                        },
+                    }
                 }
             });
         }

+ 17 - 12
app/public/js/shares/gcl_gather_compare.js

@@ -421,22 +421,27 @@ const gclCompareModel = (function () {
             return -1;
         }
 
+        const numReg = /^[0-9]+$/;
+        const hasNumReg = /[0-9]+$/;
+        const splitNumReg = /\d+|[^\d]+/g;
         function compareSubCode(code1, code2) {
-            if (numReg.test(code1)) {
-                if (numReg.test(code2)) {
-                    return parseInt(code1) - parseInt(code2);
-                } else {
-                    return -1
-                }
-            } else {
-                if (numReg.test(code2)) {
-                    return 1;
-                } else {
-                    return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); //code1.localeCompare(code2);
+            if (numReg.test(code1)) return numReg.test(code2) ? parseInt(code1) - parseInt(code2) : -1;
+            if (numReg.test(code2)) return 1;
+            if (hasNumReg.test(code1) && hasNumReg.test(code2)) {
+                const parts1 = code1.match(splitNumReg);
+                const parts2 = code2.match(splitNumReg);
+                for (let i = 0, iLen = Math.min(parts1.length, parts2.length); i < iLen; i++) {
+                    if (parts1[i] === parts2[i]) continue;
+                    if (numReg.test(parts1[i])) {
+                        return numReg.test(parts2[i]) ? parseInt(parts1[i]) - parseInt(parts2[i]) : -1;
+                    } else {
+                        return (parts1[i] < parts2[i] ? -1 : 1)
+                    }
                 }
+                return parts1.length - parts2.length;
             }
+            return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); //code1.localeCompare(code2);
         }
-        const numReg = /^[0-9]+$/;
         const aCodes = str1.split(symbol), bCodes = str2.split(symbol);
         for (let i = 0, iLength = Math.min(aCodes.length, bCodes.length); i < iLength; ++i) {
             const iCompare = compareSubCode(aCodes[i], bCodes[i]);

+ 66 - 0
app/public/js/shares/import_material.js

@@ -0,0 +1,66 @@
+const importMaterial = (function(){
+    let importNode, materials, callback;
+    const getMaterialHtml = function() {
+        const html = materials.map(x => {
+            const mh = [];
+            mh.push('<tr>');
+            mh.push(`<td class="text-center"><input type="checkbox" mid="${x.id}"></td>`);
+            mh.push(`<td class="text-center">${x.m_order}</td>`);
+            mh.push(`<td class="text-center">${x.s_order}</td>`);
+            mh.push(`<td class="text-right">${x.info_tp}</td>`);
+            mh.push(`<td class="text-right">${x.info_tax_tp}</td>`);
+            mh.push(`<td class="text-right">${x.index_tp}</td>`);
+            mh.push(`<td class="text-right">${x.index_tax_tp}</td>`);
+            mh.push(`<td class="text-right">${x.sum_tp}</td>`);
+            mh.push(`<td class="text-right">${x.sum_tax_tp}</td>`);
+            mh.push('</tr>');
+            return mh.join('');
+        });
+        return html.join('');
+    };
+    const analysisMaterial = function(data) {
+        materials = data.map(x => {
+            const md = { id: x.id, order: x.order, m_order: `第${x.order}期`, s_order: x.s_order };
+            md.info_tp = x.m_tp || 0;
+            md.info_tax_tp = x.material_tax ? (x.m_tax_tp || x.m_tp || 0) : (x.rate_tp || 0);
+            md.index_tp = x.ex_tp || 0;
+            md.index_tax_tp = x.ex_tax_tp || 0;
+            md.sum_tp = ZhCalc.add(md.info_tp, md.index_tp);
+            md.sum_tax_tp =  ZhCalc.add(md.info_tax_tp, md.index_tax_tp);
+            return md;
+        });
+        $('#import-material-list').html(getMaterialHtml())
+    };
+    const init = async function() {
+        if (materials) return;
+
+        const tenderId = window.location.pathname.split('/')[2];
+        postData(`/tender/${tenderId}/load`, { filter: 'materials' }, function(data) {
+            analysisMaterial(data.materials);
+        });
+        $('#import-material-ok').click(function() {
+            const selects = $('input[mid]:checked');
+            if (selects.length === 0) {
+                toastr.warning('请选择材料调差');
+                return;
+            }
+            const selectId = [];
+            selects.each(function(){ selectId.push(this.getAttribute('mid')); });
+            const tpField = $('input[name=im-type]:checked').val() + ($('#im-data-tax')[0].checked ? '_tax_tp' : '_tp');
+            const tpFieldStr = '{' + $('input[name=im-type]:checked').attr('type-name') + '}' + ($('#im-data-tax')[0].checked ? '{{含税金额}}' : '{{金额}}');
+            const selectMaterials = materials.filter(x => { return selectId.indexOf(x.id + '') >= 0 });
+            const tp = ZhCalc.sum(selectMaterials.map(y => { return y[tpField]; }));
+            const memo = '第{' + selectMaterials.map(y => { return y.order; }).join(',') + `}期材差-${tpFieldStr}`;
+            callback(importNode, tp, memo);
+            $('#import-material').modal('hide');
+        });
+    };
+    const show = async function(setting) {
+        importNode = setting.relaNode;
+        callback = setting.callback;
+        await init();
+        $('input[mid]').attr('checked', false);
+        $('#import-material').modal('show');
+    };
+    return { show }
+})();

+ 25 - 0
app/public/js/stage_pay.js

@@ -962,6 +962,17 @@ $(document).ready(() => {
                 }
             }
         },
+        importMaterial: function(node, tp, memo) {
+            const select = SpreadJsObj.getSelectObject(paySheet);
+            const data = { type: 'stage', updateData: { pid: select.pid, tp, postil: memo } };
+            postData(window.location.pathname + '/save', data, function (result) {
+                loadUpdateDealPays(result);
+                SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
+                paySpreadObj.loadExprToInput();
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+            });
+        }
     };
     paySpreadObj.refreshActn();
     paySpread.bind(spreadNS.Events.SelectionChanged, paySpreadObj.selectionChanged);
@@ -1227,6 +1238,20 @@ $(document).ready(() => {
                         SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                     });
                 }
+            },
+            'importMaterial': {
+                name: '导入材差数据',
+                visible: function(key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return !payBase.isSpecial(select) && !readOnly;
+                },
+                callback: function(key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    importMaterial.show({
+                        relaNode: select,
+                        callback: paySpreadObj.importMaterial,
+                    })
+                },
             }
         }
     });

+ 3 - 3
app/public/js/sub_project.js

@@ -32,7 +32,7 @@ $(document).ready(function() {
                 if (colSetCache.budget_std.show) html.push('<th width="8%">', colSetCache.budget_std.alias, '</th>');
                 if (colSetCache.tender_count.show) html.push('<th width="5%">', colSetCache.tender_count.alias, '</th>');
                 if (colSetCache.file_count.show) html.push('<th width="5%">', colSetCache.file_count.alias, '<i class="fa fa-question-circle text-primary" data-placement="bottom" data-toggle="tooltip" data-original-title="指资料归集的所有文件个数"></i>', '</th>');
-                if (colSetCache.contract_count.show) html.push('<th width="5%">', colSetCache.contract_count.alias, '<i class="fa fa-question-circle text-primary" data-placement="bottom" data-toggle="tooltip" data-original-title="项目支出合同个数"></i>', '</th>');
+                if (colSetCache.contract_count1.show) html.push('<th width="5%">', colSetCache.contract_count1.alias, '<i class="fa fa-question-circle text-primary" data-placement="bottom" data-toggle="tooltip" data-original-title="项目支出合同个数"></i>', '</th>');
                 if (colSetCache.management.show) html.push('<th width="20%">', colSetCache.management.alias, '</th>');
                 if (colSetCache.create_time.show) html.push('<th width="8%">', colSetCache.create_time.alias, '</th>');
                 if (colSetCache.control.show && canEdit) html.push('<th width="20%">', colSetCache.control.alias, '</th>');
@@ -75,7 +75,7 @@ $(document).ready(function() {
                 if (node.is_folder) {
                     if (colSetCache.tender_count.show) html.push(`<td class="text-center"></td>`);
                     if (colSetCache.file_count.show) html.push(`<td class="text-center"></td>`);
-                    if (colSetCache.contract_count.show) html.push(`<td class="text-center"></td>`);
+                    if (colSetCache.contract_count1.show) html.push(`<td class="text-center"></td>`);
                     if (colSetCache.management.show) html.push(`<td class="text-center"></td>`);
                     if (colSetCache.create_time.show) html.push(`<td class="text-center"></td>`);
                 } else {
@@ -84,7 +84,7 @@ $(document).ready(function() {
                     // 文件个数
                     if (colSetCache.file_count.show) html.push(`<td class="text-center">${node.file_count}</td>`);
                     // 合同个数
-                    if (colSetCache.contract_count.show) html.push(`<td class="text-center">${node.contract_count}</td>`);
+                    if (colSetCache.contract_count1.show) html.push(`<td class="text-center">${node.contract_count1}</td>`);
                     if (colSetCache.management.show) {
                         if (canEdit) {
                             html.push(`<td class="text-center">${node.management || ''}<a class="ml-2" href="javascript: void(0)" name="set-management"><i class="fa fa-pencil-square-o "></i></a>${node.management ? '<a class="ml-2" href="javascript: void(0)" name="refresh-management"><i class="fa fa-refresh "></i></a>': ''}</td>`);

+ 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;
         }
     }

+ 4 - 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,
@@ -374,6 +376,8 @@ module.exports = app => {
                         yf_price: p.pay_price || 0,
                         sf_price: p.settle_price || 0,
                         pay_type: p.pay_type || '',
+                        kp_price: p.kp_price || 0,
+                        kp_time: p.kp_time || null,
                         remark: '',
                         create_time: times,
                         status: auditConst.status.checked,

File diff ditekan karena terlalu besar
+ 26 - 8
app/service/contract_sp_audit.js


+ 2 - 2
app/service/cost_stage_detail.js

@@ -363,8 +363,8 @@ module.exports = app => {
             if (data.ids) return this._importContractByIds(data.ledger_id, data.cost_id, data.ids, data.months || this.ctx.costStage.stage_date, data.used);
         }
 
-        async deletePartData(transaction, tender_id, ledger_id) {
-            await transaction.delete(this.tableName, { tid, ledger_id });
+        async deletePartData(transaction, stage_id, ledger_id) {
+            await transaction.delete(this.tableName, { stage_id, ledger_id });
         }
 
         async auditCache(transaction, stageId, auditInfo) {

+ 4 - 1
app/service/stage.js

@@ -246,7 +246,10 @@ module.exports = app => {
             if (stage.status !== status.checked && stage.status !== status.uncheck) {
                 if (stage.status !== status.checkNo) {
                     // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
-                    if (stage.flowAuditors.find(x => { return x.status !== auditConst.stage.status.checking}) && stage.flowAuditorIds.indexOf(accountId) < 0) return; // 当前流程存在审批人审批通过时,不可撤回
+                    // 当前流程存在审批人 主动进行任何审批操作时,不可撤回
+                    if (stage.flowAuditors.find(x => { return x.status !== auditConst.stage.status.checking && x.status !== auditConst.stage.status.checkSkip; })
+                        && stage.flowAuditorIds.indexOf(accountId) < 0) return;
+
                     const flowAssists = stage.auditAssists.filter(x => { return stage.flowAuditorIds.indexOf(x.user_id) >= 0; });
                     if (flowAssists.find(x => { return x.confirm; })) return; //当前流程存在协审人确认时,不可撤回
                     if (stage.curAuditorIds.indexOf(accountId) < 0 && stage.flowAuditorIds.indexOf(accountId) >= 0) {

+ 63 - 99
app/service/stage_audit.js

@@ -328,7 +328,7 @@ module.exports = app => {
             try {
                 const ledgerTp = await this._updateTender(transaction);
                 const begin_time = new Date();
-                const updateData = audits.map(x => { return { id: x.id, status: auditConst.status.checking, begin_time } });
+                const updateData = audits.map(x => { return { id: x.id, status: auditConst.status.checking, begin_time, audit_locked: 0 } });
                 await transaction.updateRows(this.tableName, updateData);
                 // 计算原报最终数据
                 const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
@@ -513,7 +513,12 @@ module.exports = app => {
                         await this.ctx.service.stageSafeProd.updateHistory(this.ctx.stage, transaction);
                         await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
                         // 流程至下一审批人
-                        const updateData = nextAudits.map(x => { return { id: x.id, status: auditConst.status.checking, begin_time: time }; });
+                        const updateData = nextAudits.map(x => {
+                            return {
+                                id: x.id, status: x.audit_locked ? auditConst.status.checking : x.status,
+                                begin_time: x.audit_locked ? x.begin_time : time, audit_locked: 0,
+                            };
+                        });
                         await transaction.updateRows(this.tableName, updateData);
                         // 同步 期信息
                         await transaction.update(this.ctx.service.stage.tableName, {
@@ -658,15 +663,18 @@ module.exports = app => {
             const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
             const auditors = await this.getUniqAuditor(stageId, times); // 全部参与的审批人
             const newAuditors = auditors.map(x => {
-               return {
-                   aid: x.aid, tid: selfAudit.tid, sid: selfAudit.sid,
-                   times: times + 1, order: x.audit_order, status: auditConst.status.uncheck,
-                   audit_type: x.audit_type, audit_order: x.audit_order,
-                   audit_group: x.audit_group, audit_group_order: x.audit_group_order,
-                   audit_group_limit: x.audit_group_limit, audit_checkno_valid: x.audit_checkno_valid,
-                   audit_group_need: x.audit_group_need,
-                   audit_ledger_id: x.audit_ledger_id,
-               }
+                const locked = x.audit_locked || (x.audit_type === auditType.key.union && x.status === auditConst.status.checked && x.audit_order === selfAudit.audit_order) ? 1 : 0;
+                return {
+                    aid: x.aid, tid: selfAudit.tid, sid: selfAudit.sid,
+                    times: times + 1, order: x.audit_order, status: locked ? x.status : auditConst.status.uncheck,
+                    begin_time: locked ? x.begin_time : null, end_time: locked ? x.end_time: null,
+                    audit_type: x.audit_type, audit_order: x.audit_order,
+                    audit_group: x.audit_group, audit_group_order: x.audit_group_order,
+                    audit_group_limit: x.audit_group_limit, audit_checkno_valid: x.audit_checkno_valid,
+                    audit_group_need: x.audit_group_need,
+                    audit_ledger_id: x.audit_ledger_id,
+                    audit_locked: locked,
+                }
             });
 
             const transaction = await this.db.beginTransaction();
@@ -890,15 +898,18 @@ module.exports = app => {
                 }
                 const newFlowAuditors = [];
                 flowAudits.forEach(x => {
+                    const locked = x.audit_type === auditType.key.union && x.status === auditConst.status.checked ? 1 : 0;
                     newFlowAuditors.push({
                         tid: this.ctx.stage.tid, sid: this.ctx.stage.id, aid: x.aid,
                         times: x.times, order: selfAudit.order + 2,
-                        status: auditConst.status.uncheck,
+                        status: locked ? auditConst.status.checked : auditConst.status.uncheck,
                         audit_type: x.audit_type, audit_order: x.audit_order,
                         audit_group: x.audit_group, audit_group_order: x.audit_group_order,
                         audit_group_limit: x.audit_group_limit, audit_checkno_valid: x.audit_checkno_valid,
                         audit_group_need: x.audit_group_need,
                         audit_ledger_id: x.audit_ledger_id,
+                        audit_locked: locked,
+                        begin_time: locked ? x.begin_time : null, end_time: locked ? x.end_time : null,
                     });
                 });
                 await transaction.insert(this.tableName, newFlowAuditors);
@@ -2368,96 +2379,12 @@ module.exports = app => {
             return result;
         }
 
-        async lockConfirm4CheckNoPre(stage, uid, pre_uid, transaction) {
-            if (!transaction) throw '缺少参数';
-
-            const locked = await this.getAllDataByCondition({ where: { sid: stage.id, locked: 1, times: stage.curTimes } });
-            locked.forEach(x => {
-                x.locked_ledger_id = x.ass_ledger_id.split(',');
-            });
-            // 审批人数据锁定
-            const locking = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: uid, confirm: 1 } });
-            locking.forEach(x => {
-                x.locked_ledger_id = x.ass_ledger_id.split(',');
-            });
-            if (locking.length > 0) {
-                const updateLock = locking.map(x => { return { id: x.id, locked: 1 }; });
-                await transaction.updateRows(this.tableName, updateLock);
-            }
-            const preConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: pre_uid, confirm: 1 } });
-            // 前审批人数据,与被锁定数据相关数据确认状态保留
-            locked.push(...locking);
-            if (preConfirm.length > 0) {
-                const delConfirm = [];
-                for (const pc of preConfirm) {
-                    const bills = await this.ctx.service.ledger.getDataByCondition({ tender_id: stage.tid, ledger_id: pc.ledger_id });
-                    const billsOwner = bills ? bills.full_path.split('-') : [];
-                    const exist = locked.find(x => {
-                        for (const id of x.locked_ledger_id) {
-                            if (billsOwner.indexOf(id) >= 0) return true;
-                        }
-                        return false;
-                    });
-                    if (!exist) delConfirm.push(pc.id);
-                }
-                if (delConfirm.length > 0) await transaction.delete(this.tableName, { id: delConfirm });
-            }
-        }
-
-        async lockConfirm4CheckNo(stage, uid, auditors, transaction) {
-            if (!transaction) throw '缺少参数';
-            // 审批人数据锁定
-            const lockConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, o: uid, confirm: 1 } });
-            if (lockConfirm.length === 0) return;
-
-            const insertData = [];
-            lockConfirm.forEach(x => {
-                delete x.id;
-                x.locked = 1;
-                x.times = x.times + 1;
-                insertData.push(x);
-            });
-            // 审批人前所有角色,与锁定数据相关确认状态保留
-            const keepUid = [ stage.user_id ];
-            for (const a of auditors) {
-                if (a.aid === uid) break;
-                keepUid.push(a.aid);
-            }
-            const keepConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: keepUid, confirm: 1 } });
-            for (const kc of keepConfirm) {
-                const bills = await this.ctx.service.ledger.getDataByCondition({ tender_id: stage.tid, ledger_id: kc.ledger_id });
-                const billsOwner = bills ? bills.full_path.split('-') : [];
-                const exist = lockConfirm.find(x => {
-                    const lid = x.ass_ledger_id.split(',');
-                    for (const id of lid) {
-                        if (billsOwner.indexOf(id) >= 0) return true;
-                    }
-                    return false;
-                });
-                if (exist) {
-                    delete kc.id;
-                    kc.locked = 0;
-                    kc.times = kc.times + 1;
-                    insertData.push(kc);
-                }
-            }
-            if (insertData.length > 0) await transaction.insert(this.tableName, insertData);
-        }
-
-        async cancelLock(stage, uid, transaction) {
-            if (transaction) {
-                await transaction.update(this.tableName, { locked: 0 }, { where: { sid: stage.id, times: stage.times, aid: uid } });
-            } else {
-                await this.db.update(this.tableName, { locked: 0 }, { where: { sid: stage.id, times: stage.times, aid: uid } });
-            }
-        }
-
-        async getLockedId(stage) {
+        async getUnionLockedId(stage) {
             return await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times, audit_locked: 1 } });
         }
 
-        async getComfirmLockedId(stage) {
-            return await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times, order: stage.curOrder, status: auditConst.status.checked } });
+        async getAuditLockedId(stage) {
+            return await this.db.query(`SELECT * FROM ${this.tableName} where sid = ? and times = ? and audit_locked_lid <> ''`, [stage.id, stage.times]);
         }
 
         /**
@@ -2502,6 +2429,43 @@ module.exports = app => {
             }
             return true;
         }
+
+        async checkAuditSkip(sid) {
+            const stage = await this.ctx.service.stage.getDataById(sid);
+            if (stage.status === auditConst.status.checked) return;
+            if (stage.status === auditConst.status.uncheck || stage.status === auditConst.status.checkNo) return;
+
+            await this.service.stage.doCheckStage(stage, true);
+            if (!stage.curAuditors || stage.curAuditors[0].audit_type !== auditType.key.union) return;
+
+            const ledgerData = await this.ctx.service.ledger.getData(stage.tid);
+            const curStageData = await this.ctx.service.stageBills.getLastestStageData2(stage.tid, stage.id);
+            const checkCalcFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty'];
+            this.ctx.helper.assignRelaData(ledgerData, [
+                { data: curStageData, fields: checkCalcFields, prefix: '', relaId: 'lid' },
+            ]);
+            const Ledger = require('../lib/ledger');
+            const billsTree = new Ledger.billsTree(this.ctx, {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+                calcFields: []
+            });
+            billsTree.loadDatas(ledgerData);
+
+            const curTime = new Date();
+            const skipAudit = [];
+            for (const audit of stage.curAuditors) {
+                const auditNodes = audit.audit_ledger_id.split(',').map(x => { return billsTree.getItems(parseInt(x))});
+                if (billsTree.checkNodesUsed(auditNodes, checkCalcFields)) {
+                    skipAudit.push({ id: audit.id, status: auditConst.status.checkSkip, opinion: '协同节点未计量', end_time: curTime });
+                }
+            }
+            if (skipAudit.length === stage.curAuditors.length) skipAudit.pop();
+            if (skipAudit.length > 0) await this.db.updateRows(this.tableName, skipAudit);
+        }
     }
 
     return StageAudit;

+ 32 - 23
app/view/change/revise.ejs

@@ -26,17 +26,19 @@
                         </div>
                     </div>
                 </div>
-                <div class="d-inline-block">
-                    <a href="javascript: void(0);" name="base-opr" type="add" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus" aria-hidden="true"></i></a>
-                    <a href="javascript: void(0);" name="base-opr" type="delete" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
-                    <a href="javascript: void(0);" name="base-opr" type="up-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
-                    <a href="javascript: void(0);" name="base-opr" type="down-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
-                    <a href="javascript: void(0);" name="base-opr" type="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
-                    <a href="javascript: void(0);" name="base-opr" type="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
-                    <a href="javascript: void(0);" name="cpc" type="copy" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="复制"><i class="fa fa-files-o" aria-hidden="true"></i></a>
-                    <a href="javascript: void(0);" name="cpc" type="cut" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="剪切"><i class="fa fa-scissors" aria-hidden="true"></i></a>
-                    <a href="javascript: void(0);" name="cpc" type="paste" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="粘贴"><i class="fa fa-clipboard" aria-hidden="true"></i></a>
-                </div>
+                <% if (!ledgering) { %>
+                    <div class="d-inline-block">
+                        <a href="javascript: void(0);" name="base-opr" type="add" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                        <a href="javascript: void(0);" name="base-opr" type="delete" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                        <a href="javascript: void(0);" name="base-opr" type="up-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
+                        <a href="javascript: void(0);" name="base-opr" type="down-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
+                        <a href="javascript: void(0);" name="base-opr" type="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                        <a href="javascript: void(0);" name="base-opr" type="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                        <a href="javascript: void(0);" name="cpc" type="copy" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="复制"><i class="fa fa-files-o" aria-hidden="true"></i></a>
+                        <a href="javascript: void(0);" name="cpc" type="cut" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="剪切"><i class="fa fa-scissors" aria-hidden="true"></i></a>
+                        <a href="javascript: void(0);" name="cpc" type="paste" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="粘贴"><i class="fa fa-clipboard" aria-hidden="true"></i></a>
+                    </div>
+                <% } %>
                 <div class="d-inline-block">
                     <div class="btn-group">
                         <button href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="pos-show"><i class="fa fa-eye-slash"></i> 隐藏计量单元</button>
@@ -74,10 +76,12 @@
                                 <a class="nav-link active" href="javascript:void(0)">计量单元</a>
                             </li>
                             <li class="ml-2 nav-item">
+                                <% if (!ledgering) { %>
                                 <div class="d-inline-flex">
                                     <a href="javascript: void(0);" name="pos-opr" type="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
                                     <a href="javascript: void(0);" name="pos-opr" type="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
                                 </div>
+                                <% } %>
                             </li>
                             <li class="nav-item" id="pos-search">
                             </li>
@@ -156,24 +160,28 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
                 </li>
-                <li class="nav-item">
-                    <a class="nav-link" content="#std-xmj" href="javascript: void(0);">项目节</a>
-                </li>
-                <li class="nav-item">
-                    <a class="nav-link" content="#std-gcl" href="javascript: void(0);">标准清单</a>
-                </li>
-                <li class="nav-item">
-                    <a class="nav-link" content="#deal-bills" href="javascript: void(0);">签约清单</a>
-                </li>
+                <% if (!ledgering) { %>
+                    <li class="nav-item">
+                        <a class="nav-link" content="#std-xmj" href="javascript: void(0);">项目节</a>
+                    </li>
+                    <li class="nav-item">
+                        <a class="nav-link" content="#std-gcl" href="javascript: void(0);">标准清单</a>
+                    </li>
+                    <li class="nav-item">
+                        <a class="nav-link" content="#deal-bills" href="javascript: void(0);">签约清单</a>
+                    </li>
+                <% } %>
                 <li class="nav-item">
                     <a class="nav-link" content="#error-list" id="error-list-tab" href="javascript: void(0);" style="display: none;">错误列表</a>
                 </li>
                 <li class="nav-item">
                     <a class="nav-link" content="#check-list" id="check-list-tab" href="javascript: void(0);" style="display: none;">数据检查</a>
                 </li>
-                <li class="nav-item">
-                    <a class="nav-link" content="#dsk-list" href="javascript: void(0);">新增清单</a>
-                </li>
+                <% if (!ledgering) { %>
+                    <li class="nav-item">
+                        <a class="nav-link" content="#dsk-list" href="javascript: void(0);">新增清单</a>
+                    </li>
+                <% } %>
             </ul>
         </div>
     </div>
@@ -183,6 +191,7 @@
     const stdChapters = JSON.parse(unescape('<%- escape(JSON.stringify(stdChapters)) %>'));
     const stdBills = JSON.parse(unescape('<%- escape(JSON.stringify(stdBills)) %>'));
     const readOnly = <%- readOnly %>;
+    const ledgering = <%- ledgering %>;
     const isYb = <%- isYb %>;
     const isTz = <%- ctx.tender.data.measure_type === measureType.tz.value %>;
     const billsSpreadSetting = JSON.parse('<%- JSON.stringify(ledgerSpread) %>');

+ 18 - 0
app/view/change/revise_modal.ejs

@@ -17,6 +17,24 @@
     </div>
     <script type="text/javascript">$('#unedit2').modal('show');</script>
 <% } %>
+<% if (ledgering) { %>
+    <div class="modal fade" id="unedit3" 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">
+                    <h5>台账分解未审批通过,无法操作新增部位数据,只能勾选变更清单及填写申请变更数量。</h5>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-sm btn-primary" data-dismiss="modal">好的</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <script type="text/javascript">$('#unedit3').modal('show');</script>
+<% } %>
 <!--批量添加清单部位-->
 <div class="modal fade" id="batch" data-backdrop="static">
     <div class="modal-dialog modal-xl" role="document">

+ 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>

File diff ditekan karena terlalu besar
+ 12 - 8
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;
                                 }
                             }

+ 1 - 0
app/view/phase_pay/detail_modal.ejs

@@ -57,5 +57,6 @@
         </div>
     </div>
 </div>
+<% include ../shares/import_material_modal.ejs %>
 <% include ../shares/upload_att.ejs %>
 <% include ./audit_modal.ejs %>

+ 48 - 0
app/view/shares/import_material_modal.ejs

@@ -0,0 +1,48 @@
+<div class="modal fade" id="import-material" data-backdrop="static">
+    <div class="modal-dialog modal-xl" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">导入材差数据</h5>
+            </div>
+            <div class="modal-body">
+                <div class="" id="im-type">
+                    <div class="form-row align-items-center mb-2">
+                        <div class="col-auto"><span class="form-check-label">导入数据:</span></div>
+                        <div class="col-auto">
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" id="im-type-info" value="info" type-name="信息价调差" name="im-type" checked="">
+                                <label class="form-check-label" for="im-type-info">信息价调差</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" id="im-type-index" value="index" type-name="指数法调差" name="im-type">
+                                <label class="form-check-label" for="im-type-index">指数法调差</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" id="im-type-sum" value="sum" type-name="合计" name="im-type">
+                                <label class="form-check-label" for="im-type-sum">合计</label>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="form-row align-items-center mb-2">
+                        <div class="col-auto"><span class="form-check-label">数据类型:</span></div>
+                        <div class="form-check form-check-inline ml-2">
+                            <input class="form-check-input" type="checkbox" id="im-data-tax">
+                            <label class="form-check-label" for="im-data-tax">含税价</label>
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-height-500 scroll-y">
+                    <table class="table table-sm table-bordered table-hover" style="word-break:break-all; table-layout: fixed">
+                        <tr class="text-center"><th rowspan="2">选择</th><th rowspan="2">期数</th><th rowspan="2">关联计量</th><th colspan="2">信息价调差</th><th colspan="2">指数法调差</th><th colspan="2">合计</th></tr>
+                        <tr class="text-center"><th>价差费用</th><th>含税价</th><th>价差费用</th><th>含税价</th><th>价差费用</th><th>含税价</th></tr>
+                        <tbody id="import-material-list" class="list-table"></tbody>
+                    </table>
+                </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="import-material-ok">确定</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 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">

+ 1 - 0
app/view/stage/pay_modal.ejs

@@ -125,4 +125,5 @@
         </div>
     </div>
 </div>
+<% include ../shares/import_material_modal.ejs %>
 <% include ./audit_modal.ejs %>

+ 0 - 1
app/view/sub_proj/modal.ejs

@@ -256,7 +256,6 @@
                     <% } %>
                     </tbody>
                 </table>
-                <div class="text-danger">请勿全选(页面显示不下);谨慎修改别名(修改后,各页面概念将不一致)</div>
             </div>
             <form class="modal-footer" action="/subproj/col-set" method="post" onsubmit="return onSetCol();">
                 <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">

+ 2 - 2
config/menu.js

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

+ 2 - 0
config/web.js

@@ -459,6 +459,7 @@ const JsFiles = {
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
                     '/public/js/shares/sjs_setting.js',
                     '/public/js/shares/cs_tools.js',
+                    '/public/js/shares/import_material.js',
                     '/public/js/stage_pay.js',
                     '/public/js/stage_audit.js',
                 ],
@@ -740,6 +741,7 @@ const JsFiles = {
                     '/public/js/shares/tools_att.js',
                     '/public/js/shares/sjs_setting.js',
                     '/public/js/shares/common_audit.js',
+                    '/public/js/shares/import_material.js',
                     '/public/js/zh_calc.js',
                     '/public/js/phase_pay_detail.js',
                 ],

+ 7 - 0
sql/update.sql

@@ -35,6 +35,13 @@ 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`;
+
+ALTER TABLE `zh_stage_audit`
+ADD COLUMN `audit_locked_lid` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批退回锁定台账id' AFTER `audit_locked`;
+
 ------------------------------------
 -- 表数据
 ------------------------------------