Selaa lähdekoodia

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

Tony Kang 3 viikkoa sitten
vanhempi
commit
44fc5803d6
57 muutettua tiedostoa jossa 4603 lisäystä ja 1147 poistoa
  1. 1 1
      app/controller/file_controller.js
  2. 430 109
      app/controller/financial_controller.js
  3. 1 1
      app/controller/ledger_controller.js
  4. 3 1
      app/controller/measure_controller.js
  5. 407 0
      app/controller/quality_controller.js
  6. 1 1
      app/controller/stage_controller.js
  7. 59 0
      app/controller/wap_controller.js
  8. 53 0
      app/lib/ai_inspect.js
  9. 41 0
      app/lib/nls_token.js
  10. 36 12
      app/lib/revise_price.js
  11. 1 1
      app/middleware/tender_check.js
  12. 40 0
      app/middleware/tender_permission_check.js
  13. BIN
      app/public/css/ztree/img/diy/10.png
  14. BIN
      app/public/css/ztree/img/zTreeStandard.png
  15. 15 15
      app/public/js/change_information.js
  16. 150 32
      app/public/js/change_revise.js
  17. 4 3
      app/public/js/div_resizer.js
  18. 44 27
      app/public/js/file_detail.js
  19. 22 634
      app/public/js/financial_pay.js
  20. 262 0
      app/public/js/financial_pay_list.js
  21. 848 0
      app/public/js/financial_pay_stage.js
  22. 365 22
      app/public/js/financial_transfer_tender.js
  23. 7 1
      app/public/js/global.js
  24. 3 3
      app/public/js/shares/tender_list_order.js
  25. 9 4
      app/public/js/sr_detail.js
  26. 9 4
      app/public/js/stage.js
  27. 27 2
      app/router.js
  28. 1 1
      app/service/file.js
  29. 66 3
      app/service/financial_pay.js
  30. 8 7
      app/service/financial_pay_contract.js
  31. 262 0
      app/service/financial_pay_stage.js
  32. 45 0
      app/service/financial_pay_tender.js
  33. 11 0
      app/service/financial_transfer.js
  34. 151 20
      app/service/financial_transfer_tender.js
  35. 1 1
      app/service/stage_bills_final.js
  36. 1 1
      app/service/sub_proj_permission.js
  37. 29 26
      app/view/file/file.ejs
  38. 2 1
      app/view/file/file_modal.ejs
  39. 14 9
      app/view/financial/pay.ejs
  40. 3 0
      app/view/financial/pay_detail.ejs
  41. 197 0
      app/view/financial/pay_list.ejs
  42. 49 0
      app/view/financial/pay_list_modal.ejs
  43. 27 115
      app/view/financial/pay_modal.ejs
  44. 101 0
      app/view/financial/pay_stage.ejs
  45. 165 0
      app/view/financial/pay_stage_modal.ejs
  46. 1 1
      app/view/financial/sub_menu_list.ejs
  47. 12 8
      app/view/financial/transfer.ejs
  48. 11 46
      app/view/financial/transfer_tender.ejs
  49. 2 26
      app/view/financial/transfer_tender_modal.ejs
  50. 1 1
      app/view/profile/netcasign.ejs
  51. 1 1
      app/view/profile/sign.ejs
  52. 0 0
      app/view/quality/lab_modal.ejs
  53. 32 7
      app/view/stage/modal.ejs
  54. 423 0
      app/view/wap/inspection.ejs
  55. 42 0
      config/web.js
  56. 80 0
      db_script/financial_transfer.js
  57. 27 0
      sql/update.sql

+ 1 - 1
app/controller/file_controller.js

@@ -61,7 +61,7 @@ module.exports = app => {
                 renderData.categoryData = await ctx.service.category.getAllCategory(ctx.subProject);
                 renderData.canFiling = ctx.subProject.permission.file_permission.indexOf(ctx.service.subProjPermission.PermissionConst.file.filing.value) >= 0;
                 renderData.canUpload = ctx.subProject.permission.file_permission.indexOf(ctx.service.subProjPermission.PermissionConst.file.upload.value) >= 0;
-                renderData.canDelete = ctx.subProject.permission.file_permission.indexOf(ctx.service.subProjPermission.PermissionConst.file.delete.value) >= 0;
+                renderData.canEdit = ctx.subProject.permission.file_permission.indexOf(ctx.service.subProjPermission.PermissionConst.file.editfile.value) >= 0;
                 renderData.fileReferenceList = await ctx.service.subProject.getFileReference(ctx.subProject, ctx.service.subProject.FileReferenceType.file);
                 await this.layout('file/file.ejs', renderData, 'file/file_modal.ejs');
             } catch (err) {

+ 430 - 109
app/controller/financial_controller.js

@@ -189,54 +189,36 @@ module.exports = app => {
                 if (!transferInfo) {
                     throw '该资金划拨信息不存在';
                 }
+                const categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
                 const transferTenderList = await ctx.service.financialTransferTender.getList(trid, true);
-                const total = await ctx.service.financialTransferTender.count({ trid });
-                // 分页相关
-                const page = ctx.page;
-                const pageSize = ctx.pageSize;
-                const pageInfo = {
-                    page,
-                    pageSizeSelect: 1,
-                    pageSize,
-                    total_num: total,
-                    total: Math.ceil(total / pageSize),
-                    queryData: JSON.stringify(ctx.urlInfo.query),
-                };
                 const tenders = await ctx.service.tender.getAllDataByCondition({ where: { spid: ctx.subProject.id, filter_fund: 0 } });
-                for (const tt of transferTenderList) {
-                    const tenderIndex = ctx.helper._.findIndex(tenders, { id: tt.tid });
-                    if (tenderIndex !== -1) {
-                        tenders.splice(tenderIndex, 1);
+                const needTenders = [];
+                for (const t of tenders) {
+                    await ctx.service.tenderCache.loadTenderCache(t, this.ctx.session.sessionUser.accountId);
+                    if (t.stage_complete_count && t.stage_complete_count >= 1) {
+                        t.category = t.category && t.category !== '' ? JSON.parse(t.category) : null;
+                        needTenders.push(t);
                     }
                 }
-                const allTransferTenders = await ctx.service.financialTransferTender.getAllDataByCondition({ where: { spid: ctx.subProject.id } });
-                for (const t of tenders) {
-                    const stages = await ctx.service.stage.getAllDataByCondition({ where: { tid: t.id, status: auditConst.stage.status.checked } });
-                    const allTenderStages = ctx.helper._.filter(allTransferTenders, { tid: t.id });
-                    for (const ts of allTenderStages) {
-                        const sorderArray = ts.sorder ? ts.sorder.split(',') : [];
-                        for (const sorder of sorderArray) {
-                            const stageIndex = ctx.helper._.findIndex(stages, function(item) {
-                                return item.order === parseInt(sorder);
-                            });
-                            if (stageIndex !== -1) {
-                                stages.splice(stageIndex, 1);
-                            }
-                        }
+                for (const tt of transferTenderList) {
+                    const tenderIndex = ctx.helper._.findIndex(needTenders, { id: tt.tid });
+                    if (tenderIndex !== -1) {
+                        // 合并tenders[tenderIndex]到tt
+                        const cloneTender = ctx.helper._.cloneDeep(needTenders[tenderIndex]);
+                        delete cloneTender.id;
+                        Object.assign(tt, cloneTender);
                     }
-                    t.stages = stages;
                 }
-
                 const renderData = {
                     financialPermission,
                     transferInfo,
                     transferTenderList,
-                    tenders,
+                    categoryData,
+                    tenders: needTenders,
                     moment,
                     preUrl: '/sp/' + ctx.subProject.id + '/financial/transfer',
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.financial.transferTender),
                     whiteList: ctx.app.config.multipart.whitelist,
-                    pageInfo,
                 };
                 await this.layout('financial/transfer_tender.ejs', renderData, 'financial/transfer_tender_modal.ejs');
             } catch (err) {
@@ -298,10 +280,13 @@ module.exports = app => {
                         }
                         break;
                     case 'del-tender':
-                        responseData.data = await ctx.service.financialTransferTender.delTenders(data.postData.node);
+                        responseData.data = await ctx.service.financialTransferTender.delTenders(transferInfo, data.postData.node);
                         break;
                     case 'update-hb_tp':
-                        responseData.data = await ctx.service.financialTransferTender.updateHbTp(data.postData.node, data.postData.hb_tp);
+                        responseData.data = await ctx.service.financialTransferTender.updateHbTp(transferInfo, data.postData.node, data.postData.hb_tp);
+                        break;
+                    case 'update-tender':
+                        responseData.data = await ctx.service.financialTransferTender.updateTender(transferInfo, data.postData);
                         break;
                     default:
                         throw '未知操作';
@@ -584,36 +569,18 @@ module.exports = app => {
          * @param {Object} ctx - egg全局变量
          * @return {void}
          */
-        async pay(ctx) {
+        async payStage(ctx) {
             try {
-                const status = parseInt(ctx.query.status) || 0;
-                const tid = parseInt(ctx.query.tid) || null;
-                const used = ctx.query.used || null;
-                await this._filterPay(ctx, status, tid, used);
+                const company = ctx.query.company || null;
+                const qi = parseInt(ctx.query.qi) || null;
+                await this._filterPayStage(ctx, company, qi);
             } catch (err) {
                 this.log(err);
                 ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
             }
         }
 
-        // /**
-        //  * 变更管理 状态筛选 页面 (Get)
-        //  * @param {Object} ctx - egg全局变量
-        //  * @return {void}
-        //  */
-        // async payStatus(ctx) {
-        //     try {
-        //         const status = parseInt(ctx.query.status);
-        //         const tid = parseInt(ctx.params.tid) || null;
-        //         const used = ctx.params.used || null;
-        //         await this._filterPay(ctx, status, tid, used);
-        //     } catch (err) {
-        //         this.logger.error(err);
-        //         ctx.redirect('/financial');
-        //     }
-        // }
-
-        async _filterPay(ctx, status = 0, tid = null, used = null) {
+        async _filterPayStage(ctx, company = null, qi = null) {
             const financialPermission = await ctx.service.subProjPermission.getFinancailPermission(ctx.subProject.permission.fund_trans_permission, ctx.subProject.permission.fund_pay_permission);
             if (!financialPermission.pay_show) {
                 throw '没有查看权限';
@@ -621,7 +588,7 @@ module.exports = app => {
             const fptAudits = await ctx.service.financialPayTenderAudit.getAllDataByCondition({ where: { spid: ctx.subProject.id, uid: ctx.session.sessionUser.accountId } });
             const fptAuditTids = ctx.helper._.map(fptAudits, 'tid');
             const fptReportTids = ctx.helper._.map(ctx.helper._.filter(fptAudits, { is_report: 1 }), 'tid');
-            const filterTids = tid === null ? (ctx.session.sessionUser.is_admin ? null : fptAuditTids) : [tid];
+            const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
             const tenderCondition = { spid: ctx.subProject.id, filter_fund: 0 };
             let hadTender = false;
             if (ctx.session.sessionUser.is_admin) {
@@ -633,15 +600,19 @@ module.exports = app => {
                 hadTender = false;
                 tenderCondition.id = -1;
             }
-            const tenders = hadTender ? await ctx.service.tender.getAllDataByCondition({ where: tenderCondition, columns: ['id', 'name'] }) : [];
-            const filter = JSON.parse(JSON.stringify(auditConst.financial.filter));
-            filter.count = [];
-            filter.count[filter.status.pending] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, filter.status.pending, filterTids, used);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
-            filter.count[filter.status.uncheck] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, filter.status.uncheck, filterTids, used);// await ctx.service.change.checkingDatas(tender.id, ctx.session.sessionUser.accountId);
-            filter.count[filter.status.checking] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, filter.status.checking, filterTids, used);// await ctx.service.change.checkedDatas(tender.id, ctx.session.sessionUser.accountId);
-            filter.count[filter.status.checked] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, filter.status.checked, filterTids, used);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
-            const payList = await ctx.service.financialPay.getListByStatus(ctx.subProject.id, status, filterTids, used, 1);
-            const total = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, status, filterTids, used);
+            const categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
+            const tenders = hadTender ? await ctx.service.tender.getAllDataByCondition({ where: tenderCondition, columns: ['id', 'name', 'category'] }) : [];
+            const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'account', 'name', 'company', 'company_id', 'role', 'enable', 'is_admin', 'account_group', 'mobile']);
+            const user = accountList.find(item => item.id === ctx.session.sessionUser.accountId) || null;
+            const userCompany = user ? ctx.helper._.find(unitList, { name: user.company }) : null;
+            if (!userCompany) {
+                throw '请联系管理员添加用户所在单位信息';
+            }
+            const userOrderList = await ctx.service.financialPayStage.getUserOrderList(ctx.subProject.id, userCompany ? userCompany.id : 0);
+            const userCompanyList = await ctx.service.financialPayStage.getUserCompanyList(ctx.subProject.id, userCompany ? userCompany.id : 0, unitList);
+            const companyInfo = company ? ctx.helper._.find(unitList, { name: company }) : null;
+            const payList = await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, companyInfo ? companyInfo.id : null, userCompany ? userCompany.id : 0, qi);
+            const total = await ctx.service.financialPayStage.getCountByStatus(ctx.subProject.id, companyInfo ? companyInfo.id : null, userCompany ? userCompany.id : 0, qi);
             // 分页相关
             const page = ctx.page;
             const pageSize = ctx.pageSize;
@@ -653,45 +624,25 @@ module.exports = app => {
                 total: Math.ceil(total / pageSize),
                 queryData: JSON.stringify(ctx.urlInfo.query),
             };
-            // 获取所有项目参与者
-            const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'account', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile']);
             const payTenders = await ctx.service.financialPayTender.getAllDataByCondition({ where: { spid: ctx.subProject.id } });
             if (tenders.length > 0) {
-                const allLastPay = await ctx.service.financialPay.getAllDataByCondition({ where: { spid: ctx.subProject.id, tid: ctx.helper._.map(tenders, 'id') }, columns: ['id', 'code'], orders: [['id', 'desc']] });
                 const tenderInfos = await ctx.service.tenderInfo.getAllDataByCondition({ where: { tid: ctx.helper._.map(tenders, 'id') }, columns: ['tid', 'pay_account', 'deal_info'] });
                 for (const t of tenders) {
+                    t.category = t.category && t.category !== '' ? JSON.parse(t.category) : null;
                     const info = ctx.helper._.find(tenderInfos, { tid: t.id });
                     t.pay_account = info && info.pay_account ? JSON.parse(info.pay_account).project : '';
                     const pt = ctx.helper._.find(payTenders, { tid: t.id });
                     t.pt = pt ? pt : { id: 0, tid: t.id, name: '', bank: '', bank_account: '', contact: '', phone: '' };
                     if (ctx.session.sessionUser.is_admin || ctx.helper._.includes(fptReportTids, t.id)) {
                         t.dealCode = info && info.deal_info ? JSON.parse(info.deal_info).dealCode : '';
-                        const lastPay = ctx.helper._.filter(allLastPay, { tid: t.id });
-                        t.startNum = 1;
-                        if (lastPay.length > 0) {
-                            const startArray = lastPay[0].code.split('-');
-                            t.startNum = parseInt(startArray[startArray.length - 1]) + 1;
-                        }
                     }
                 }
             }
             for (const pay of payList) {
-                const t = ctx.helper._.find(tenders, { id: pay.tid });
-                pay.tenderName = t ? t.name : '';
+                const t = ctx.helper._.find(unitList, { id: pay.company_id });
+                pay.company = t ? t.name : '';
                 const userInfo = ctx.helper._.find(accountList, { id: pay.uid });
                 pay.username = userInfo ? userInfo.name : '';
-                if (pay.status !== auditConst.financial.status.checked || !pay.final_auditor_str) {
-                    pay.curAuditors = await ctx.service.financialPayAudit.getAuditorsByStatus(pay.id, pay.status, pay.times);
-                    if (pay.status === auditConst.financial.status.checked && pay.curAuditors.length > 0) {
-                        const final_auditor_str = pay.curAuditors[0].audit_type === auditConst.auditType.key.common
-                            ? pay.curAuditors[0].name + (pay.curAuditors[0].role ? '-' + pay.curAuditors[0].role : '')
-                            : ctx.helper.transFormToChinese(pay.curAuditors[0].audit_order) + '审';
-                        await ctx.service.financialPay.defaultUpdate({ final_auditor_str }, { where: { id: pay.id } });
-                    }
-                }
-                if (pay.status !== auditConst.financial.status.checked) {
-                    pay.entities = await ctx.service.financialPayContract.getEntities(pay.id);
-                }
             }
             if (ctx.session.sessionUser.is_admin && tenders.length > 0) {
                 const allPermissionList = await ctx.service.financialPayTenderAudit.getAllList(ctx.subProject.id, ctx.helper._.map(tenders, 'id'), accountList);
@@ -703,25 +654,26 @@ module.exports = app => {
                 }
             }
             const renderData = {
+                categoryData,
                 tenders,
                 financialPermission,
                 usedList: financialConst.used,
                 auditConst: auditConst.financial,
-                filter,
-                tid,
-                status,
-                used,
+                company,
+                qi,
                 payList,
                 fptAuditTids,
                 fptReportTids,
+                userCompanyList,
+                userOrderList,
+                unitList,
                 moment,
+                user,
                 auditType: auditConst.auditType,
-                // preUrl: '/financial',
-                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.financial.pay),
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.financial.payStage),
                 pageInfo,
             };
             if (ctx.session.sessionUser.is_admin) {
-                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
                 renderData.accountList = accountList;
                 renderData.accountGroup = unitList.map(item => {
                     const groupList = accountList.filter(item1 => item1.company === item.name);
@@ -730,10 +682,10 @@ module.exports = app => {
                 renderData.auditType = auditConst.auditType;
                 renderData.shenpi = shenpiConst;
             }
-            await this.layout('financial/pay.ejs', renderData, 'financial/pay_modal.ejs');
+            await this.layout('financial/pay_stage.ejs', renderData, 'financial/pay_stage_modal.ejs');
         }
 
-        async paySave(ctx) {
+        async payStageSave(ctx) {
             try {
                 const responseData = {
                     err: 0, msg: '', data: {},
@@ -785,15 +737,208 @@ module.exports = app => {
                     case 'copy-shenpi-audit':
                         responseData.data = await ctx.service.financialPayTenderAudit.copyShenpi2otherTender(ctx.subProject.id, data, data.this_tid);
                         break;
-                    case 'add-pay':
-                        responseData.data = await ctx.service.financialPay.addPay(ctx.subProject.id, data.updateData);
+                    case 'copy-tender-bank':
+                        responseData.data = await ctx.service.financialPayTender.copyBank2otherTender(ctx.subProject.id, data.tenders, data.this_tender);
                         break;
-                    case 'del-pay':
-                        responseData.data = await ctx.service.financialPay.delPay(data.postData.node);
+                    case 'add-pay-stage':
+                        responseData.data = await ctx.service.financialPayStage.addPayStage(ctx.subProject.id, data.updateData);
+                        break;
+                    case 'del-pay-stage':
+                        responseData.data = await ctx.service.financialPayStage.delPayStage(data.postData.node);
                         break;
                     case 'set-pay-tender':
                         responseData.data = await ctx.service.financialPayTender.savePayTender(ctx.subProject.id, data.updateData);
                         break;
+                    default: throw '参数有误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: {} };
+            }
+        }
+
+        /**
+         * 变更管理 页面 (Get)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async pay(ctx) {
+            try {
+                const status = parseInt(ctx.query.status) || 0;
+                const tid = parseInt(ctx.query.tid) || null;
+                const used = ctx.query.used || null;
+                const from = ctx.query.from || null;
+                await this._filterPay(ctx, status, tid, used, from);
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
+            }
+        }
+
+        async _filterPay(ctx, status = 0, tid = null, used = null, from) {
+            const financialPermission = await ctx.service.subProjPermission.getFinancailPermission(ctx.subProject.permission.fund_trans_permission, ctx.subProject.permission.fund_pay_permission);
+            if (!financialPermission.pay_show) {
+                throw '没有查看权限';
+            }
+            const payStage = await ctx.service.financialPayStage.getOnePayStage(ctx.params.fpsid);
+            if (!payStage) {
+                throw '该支付单位期不存在';
+            }
+            const fptAudits = await ctx.service.financialPayTenderAudit.getAllDataByCondition({ where: { spid: ctx.subProject.id, uid: ctx.session.sessionUser.accountId } });
+            const fptAuditTids = ctx.helper._.map(fptAudits, 'tid');
+            const fptReportTids = ctx.helper._.map(ctx.helper._.filter(fptAudits, { is_report: 1 }), 'tid');
+            const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
+            const filterTids = tid === null ? (ctx.session.sessionUser.is_admin ? null : fptAuditTids) : [tid];
+            const tenderCondition = { spid: ctx.subProject.id, filter_fund: 0 };
+            let hadTender = false;
+            if (ctx.session.sessionUser.is_admin) {
+                hadTender = true;
+            } else if (fptAuditTids.length !== 0) {
+                hadTender = true;
+                tenderCondition.id = fptAuditTids;
+            } else {
+                hadTender = false;
+                tenderCondition.id = -1;
+            }
+            const categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
+            const tenders = hadTender ? await ctx.service.tender.getAllDataByCondition({ where: tenderCondition, columns: ['id', 'name', 'category'] }) : [];
+            const filter = JSON.parse(JSON.stringify(auditConst.financial.filter));
+            filter.count = [];
+            filter.count[filter.status.pending] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, payStage.id, filter.status.pending, filterTids, used);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
+            filter.count[filter.status.uncheck] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, payStage.id, filter.status.uncheck, filterTids, used);// await ctx.service.change.checkingDatas(tender.id, ctx.session.sessionUser.accountId);
+            filter.count[filter.status.checking] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, payStage.id, filter.status.checking, filterTids, used);// await ctx.service.change.checkedDatas(tender.id, ctx.session.sessionUser.accountId);
+            filter.count[filter.status.checked] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, payStage.id, filter.status.checked, filterTids, used);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
+            const payList = await ctx.service.financialPay.getListByStatus(ctx.subProject.id, payStage.id, status, filterTids, used, 1);
+            const total = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, payStage.id, status, filterTids, used);
+            // 分页相关
+            const page = ctx.page;
+            const pageSize = ctx.pageSize;
+            const pageInfo = {
+                page,
+                pageSizeSelect: 1,
+                pageSize,
+                total_num: total,
+                total: Math.ceil(total / pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+            // 获取所有项目参与者
+            const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'account', 'name', 'company', 'company_id', 'role', 'enable', 'is_admin', 'account_group', 'mobile']);
+            const payTenders = await ctx.service.financialPayTender.getAllDataByCondition({ where: { spid: ctx.subProject.id } });
+            if (tenders.length > 0) {
+                const allLastPay = await ctx.service.financialPay.getAllDataByCondition({ where: { spid: ctx.subProject.id, tid: ctx.helper._.map(tenders, 'id') }, columns: ['id', 'tid', 'code'], orders: [['id', 'desc']] });
+                const tenderInfos = await ctx.service.tenderInfo.getAllDataByCondition({ where: { tid: ctx.helper._.map(tenders, 'id') }, columns: ['tid', 'pay_account', 'deal_info'] });
+                for (const t of tenders) {
+                    t.category = t.category && t.category !== '' ? JSON.parse(t.category) : null;
+                    const info = ctx.helper._.find(tenderInfos, { tid: t.id });
+                    t.pay_account = info && info.pay_account ? JSON.parse(info.pay_account).project : '';
+                    const pt = ctx.helper._.find(payTenders, { tid: t.id });
+                    t.pt = pt ? pt : { id: 0, tid: t.id, name: '', bank: '', bank_account: '', contact: '', phone: '' };
+                    if (ctx.session.sessionUser.is_admin || ctx.helper._.includes(fptReportTids, t.id)) {
+                        t.dealCode = info && info.deal_info ? JSON.parse(info.deal_info).dealCode : '';
+                        const lastPay = ctx.helper._.filter(allLastPay, { tid: t.id });
+                        t.startNum = 1;
+                        if (lastPay.length > 0) {
+                            const startArray = lastPay[0].code.split('-');
+                            t.startNum = parseInt(startArray[startArray.length - 1]) + 1;
+                        }
+                    }
+                }
+            }
+            for (const pay of payList) {
+                const t = ctx.helper._.find(tenders, { id: pay.tid });
+                pay.tenderName = t ? t.name : '';
+                const userInfo = ctx.helper._.find(accountList, { id: pay.uid });
+                pay.username = userInfo ? userInfo.name : '';
+                if (pay.status !== auditConst.financial.status.checked || !pay.final_auditor_str) {
+                    pay.curAuditors = await ctx.service.financialPayAudit.getAuditorsByStatus(pay.id, pay.status, pay.times);
+                    if (pay.status === auditConst.financial.status.checked && pay.curAuditors.length > 0) {
+                        const final_auditor_str = pay.curAuditors[0].audit_type === auditConst.auditType.key.common
+                            ? pay.curAuditors[0].name + (pay.curAuditors[0].role ? '-' + pay.curAuditors[0].role : '')
+                            : ctx.helper.transFormToChinese(pay.curAuditors[0].audit_order) + '审';
+                        await ctx.service.financialPay.defaultUpdate({ final_auditor_str }, { where: { id: pay.id } });
+                    }
+                }
+                if (pay.status !== auditConst.financial.status.checked) {
+                    pay.entities = await ctx.service.financialPayContract.getEntities(pay.id);
+                }
+            }
+            if (ctx.session.sessionUser.is_admin && tenders.length > 0) {
+                const allPermissionList = await ctx.service.financialPayTenderAudit.getAllList(ctx.subProject.id, ctx.helper._.map(tenders, 'id'), accountList);
+                const allAuditGroupList = await ctx.service.shenpiAudit.getAllAuditGroupList(ctx.helper._.map(tenders, 'id'), shenpiConst.sp_other_type.financial, shenpiConst.sp_status.gdspl);
+                for (const t of tenders) {
+                    t.permissionList = ctx.helper._.filter(allPermissionList, { tid: t.id }) || [];
+                    // t.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(t.id, shenpiConst.sp_other_type.financial, shenpiConst.sp_status.gdspl);
+                    t.auditGroupList = ctx.helper._.find(allAuditGroupList, { tid: t.id }) ? ctx.helper._.find(allAuditGroupList, { tid: t.id }).audits : [];
+                }
+            }
+            const notStagePays = await ctx.service.financialPay.getAllDataByCondition({ where: { spid: ctx.subProject.id, fpsid: null } });
+            for (const pay of notStagePays) {
+                const t = ctx.helper._.find(tenders, { id: pay.tid });
+                pay.tenderName = t ? t.name : '';
+                const userInfo = ctx.helper._.find(accountList, { id: pay.uid });
+                pay.username = userInfo ? userInfo.name : '';
+            }
+            const renderData = {
+                categoryData,
+                tenders,
+                financialPermission,
+                usedList: financialConst.used,
+                auditConst: auditConst.financial,
+                filter,
+                tid,
+                status,
+                used,
+                from,
+                payList,
+                payStage,
+                notStagePays,
+                fptAuditTids,
+                fptReportTids,
+                unitList,
+                moment,
+                auditType: auditConst.auditType,
+                preUrl: '/sp/' + ctx.subProject.id + '/financial/pay' + (from ? '/list' : '/stage'),
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.financial.pay),
+                pageInfo,
+            };
+            if (ctx.session.sessionUser.is_admin) {
+                renderData.accountList = accountList;
+                renderData.accountGroup = unitList.map(item => {
+                    const groupList = accountList.filter(item1 => item1.company === item.name);
+                    return { groupName: item.name, groupList };
+                }).filter(x => { return x.groupList.length > 0; });
+                renderData.auditType = auditConst.auditType;
+                renderData.shenpi = shenpiConst;
+            }
+            await this.layout('financial/pay.ejs', renderData, 'financial/pay_modal.ejs');
+        }
+
+        async paySave(ctx) {
+            try {
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.type) {
+                    throw '提交数据错误';
+                }
+                const payStage = await ctx.service.financialPayStage.getDataById(ctx.params.fpsid);
+                const fileds = ['batch-old-pays', 'add-pay'];
+                if (!payStage && fileds.indexOf(data.type) !== -1) {
+                    throw '资金支付单位期不存在';
+                }
+                switch (data.type) {
+                    case 'batch-old-pays':
+                        await ctx.service.financialPay.batchOldPays(ctx.subProject.id, payStage, data.postData.payIds);
+                        break;
+                    case 'add-pay':
+                        responseData.data = await ctx.service.financialPay.addPay(ctx.subProject.id, payStage, data.updateData);
+                        break;
+                    case 'del-pay':
+                        responseData.data = await ctx.service.financialPay.delPay(data.postData.node);
+                        break;
                     case 'get-auditors':
                         const fpInfo = await ctx.service.financialPay.getDataById(data.id);
                         await ctx.service.financialPay.loadPayUser(fpInfo);
@@ -810,6 +955,161 @@ module.exports = app => {
         }
 
         /**
+         * 变更管理 页面 (Get)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async payList(ctx) {
+            try {
+                const company = ctx.query.company || null;
+                const qi = parseInt(ctx.query.qi) || null;
+                const status = parseInt(ctx.query.status) || 0;
+                const tid = parseInt(ctx.query.tid) || null;
+                const used = ctx.query.used || null;
+                await this._filterPayList(ctx, company, qi, status, tid, used);
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
+            }
+        }
+
+        async _filterPayList(ctx, company = null, qi = null, status = 0, tid = null, used = null) {
+            const financialPermission = await ctx.service.subProjPermission.getFinancailPermission(ctx.subProject.permission.fund_trans_permission, ctx.subProject.permission.fund_pay_permission);
+            if (!financialPermission.pay_show) {
+                throw '没有查看权限';
+            }
+            const fptAudits = await ctx.service.financialPayTenderAudit.getAllDataByCondition({ where: { spid: ctx.subProject.id, uid: ctx.session.sessionUser.accountId } });
+            const fptAuditTids = ctx.helper._.map(fptAudits, 'tid');
+            const fptReportTids = ctx.helper._.map(ctx.helper._.filter(fptAudits, { is_report: 1 }), 'tid');
+            const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
+            const filterTids = tid === null ? (ctx.session.sessionUser.is_admin ? null : fptAuditTids) : [tid];
+            const tenderCondition = { spid: ctx.subProject.id, filter_fund: 0 };
+            let hadTender = false;
+            if (ctx.session.sessionUser.is_admin) {
+                hadTender = true;
+            } else if (fptAuditTids.length !== 0) {
+                hadTender = true;
+                tenderCondition.id = fptAuditTids;
+            } else {
+                hadTender = false;
+                tenderCondition.id = -1;
+            }
+            const categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
+            const tenders = hadTender ? await ctx.service.tender.getAllDataByCondition({ where: tenderCondition, columns: ['id', 'name', 'category'] }) : [];
+            const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'account', 'name', 'company', 'company_id', 'role', 'enable', 'is_admin', 'account_group', 'mobile']);
+            const user = accountList.find(item => item.id === ctx.session.sessionUser.accountId) || null;
+            const userCompany = user ? ctx.helper._.find(unitList, { name: user.company }) : null;
+            if (!userCompany) {
+                throw '请联系管理员添加用户所在单位信息';
+            }
+            const userOrderList = await ctx.service.financialPayStage.getUserOrderList(ctx.subProject.id, userCompany ? userCompany.id : 0);
+            const userCompanyList = await ctx.service.financialPayStage.getUserCompanyList(ctx.subProject.id, userCompany ? userCompany.id : 0, unitList);
+            let fpsidList = [];
+            if (company || qi) {
+                const companyInfo = company ? ctx.helper._.find(unitList, { name: company }) : null;
+                const fpstageList = await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, companyInfo ? companyInfo.id : null, userCompany ? userCompany.id : 0, qi, 0);
+                fpsidList = ctx.helper._.map(fpstageList, 'id');
+            }
+            // const payList = await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, companyInfo ? companyInfo.id : null, userCompany ? userCompany.id : 0, qi);
+            const filter = JSON.parse(JSON.stringify(auditConst.financial.filter));
+            filter.count = [];
+            filter.count[filter.status.pending] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, fpsidList.length > 0 ? fpsidList : null, filter.status.pending, filterTids, used);
+            filter.count[filter.status.uncheck] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, fpsidList.length > 0 ? fpsidList : null, filter.status.uncheck, filterTids, used);
+            filter.count[filter.status.checking] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, fpsidList.length > 0 ? fpsidList : null, filter.status.checking, filterTids, used);
+            filter.count[filter.status.checked] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, fpsidList.length > 0 ? fpsidList : null, filter.status.checked, filterTids, used);
+            const payList = await ctx.service.financialPay.getListByStatus(ctx.subProject.id, fpsidList.length > 0 ? fpsidList : null, status, filterTids, used, 1);
+            const total = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, fpsidList.length > 0 ? fpsidList : null, status, filterTids, used);
+            // 分页相关
+            const page = ctx.page;
+            const pageSize = ctx.pageSize;
+            const pageInfo = {
+                page,
+                pageSizeSelect: 1,
+                pageSize,
+                total_num: total,
+                total: Math.ceil(total / pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+            for (const pay of payList) {
+                const t = ctx.helper._.find(tenders, { id: pay.tid });
+                pay.tenderName = t ? t.name : '';
+                const userInfo = ctx.helper._.find(accountList, { id: pay.uid });
+                pay.username = userInfo ? userInfo.name : '';
+                if (pay.status !== auditConst.financial.status.checked || !pay.final_auditor_str) {
+                    pay.curAuditors = await ctx.service.financialPayAudit.getAuditorsByStatus(pay.id, pay.status, pay.times);
+                    if (pay.status === auditConst.financial.status.checked && pay.curAuditors.length > 0) {
+                        const final_auditor_str = pay.curAuditors[0].audit_type === auditConst.auditType.key.common
+                            ? pay.curAuditors[0].name + (pay.curAuditors[0].role ? '-' + pay.curAuditors[0].role : '')
+                            : ctx.helper.transFormToChinese(pay.curAuditors[0].audit_order) + '审';
+                        await ctx.service.financialPay.defaultUpdate({ final_auditor_str }, { where: { id: pay.id } });
+                    }
+                }
+                if (pay.status !== auditConst.financial.status.checked) {
+                    pay.entities = await ctx.service.financialPayContract.getEntities(pay.id);
+                }
+                pay.stage = pay.fpsid ? await ctx.service.financialPayStage.getOnePayStage(pay.fpsid) : null;
+            }
+            const renderData = {
+                categoryData,
+                tenders,
+                financialPermission,
+                usedList: financialConst.used,
+                auditConst: auditConst.financial,
+                filter,
+                tid,
+                status,
+                used,
+                company,
+                qi,
+                userCompanyList,
+                userOrderList,
+                payList,
+                fptAuditTids,
+                fptReportTids,
+                unitList,
+                moment,
+                auditType: auditConst.auditType,
+                // preUrl: '/sp/' + ctx.subProject.id + '/financial/pay' + (from === 'list' ? '/list/?from=' + from : '/stage'),
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.financial.payList),
+                pageInfo,
+            };
+            await this.layout('financial/pay_list.ejs', renderData, 'financial/pay_list_modal.ejs');
+        }
+
+        async payListSave(ctx) {
+            try {
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.type) {
+                    throw '提交数据错误';
+                }
+                const payStage = await ctx.service.financialPayStage.getDataById(ctx.params.fpsid);
+                if (!payStage) {
+                    throw '资金支付单位期不存在';
+                }
+                switch (data.type) {
+                    case 'batch-old-pays':
+                        await ctx.service.financialPay.batchOldPays(ctx.subProject.id, payStage, data.postData.payIds);
+                        break;
+                    case 'add-pay':
+                        responseData.data = await ctx.service.financialPay.addPay(ctx.subProject.id, payStage, data.updateData);
+                        break;
+                    case 'del-pay':
+                        responseData.data = await ctx.service.financialPay.delPay(payStage, data.postData.node);
+                        break;
+                    default: throw '参数有误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: {} };
+            }
+        }
+
+        /**
          * 获取审批界面所需的 原报、审批人数据等
          * @param ctx
          * @return {Promise<void>}
@@ -826,6 +1126,7 @@ module.exports = app => {
                 }
                 await this._getFinancialAuditViewData(ctx);
                 // 获取附件列表
+                const from = ctx.query.from || null;
                 const fileList = await ctx.service.financialPayAtt.getAtt(ctx.financialPay.id);
                 const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
                 const auth_mobile = pa.auth_mobile;
@@ -838,8 +1139,10 @@ module.exports = app => {
                     auditType: auditConst.auditType,
                     whiteList: ctx.app.config.multipart.whitelist,
                     moment,
+                    from,
                     fileList,
                     authMobile: auth_mobile,
+                    returnUrl: `/sp/${ctx.subProject.id}/financial/pay/${from === 'list' ? 'list' : 'stage/' + ctx.financialPay.fpsid}`,
                     preUrl: `/sp/${ctx.subProject.id}/financial/pay`,
                     preUrl2: `/sp/${ctx.subProject.id}/financial/pay/` + ctx.financialPay.id,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.financial.payDetail),
@@ -862,6 +1165,9 @@ module.exports = app => {
                 if (!ctx.financialPay) {
                     throw '数据错误';
                 }
+                if (!ctx.financialPay.fpsid || !ctx.financialPay.payStage) {
+                    throw '未关联资金支付单位期无法进行任何操作';
+                }
                 if (ctx.financialPay.uid !== ctx.session.sessionUser.accountId) {
                     throw '您无权上报该资金支付数据';
                 }
@@ -889,6 +1195,9 @@ module.exports = app => {
                 if (!ctx.financialPay || ctx.financialPay.status !== auditConst.financial.status.checking) {
                     throw '当前资金支付数据有误';
                 }
+                if (!ctx.financialPay.fpsid || !ctx.financialPay.payStage) {
+                    throw '未关联资金支付单位期无法进行任何操作';
+                }
                 if (ctx.financialPay.curAuditorIds.length === 0 || ctx.financialPay.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) === -1) {
                     throw '您无权进行该操作';
                 }
@@ -916,6 +1225,9 @@ module.exports = app => {
          */
         async checkPayAgain(ctx) {
             try {
+                if (!ctx.financialPay.fpsid || !ctx.financialPay.payStage) {
+                    throw '未关联资金支付单位期无法进行任何操作';
+                }
                 // 获取终审
                 // const auditInfo = await this.ctx.service.changeProjectAudit.getAuditorByStatus(ctx.change.id, audit.changeProject.status.checked);
                 // if (ctx.change.status !== audit.changeProject.status.checked || ctx.session.sessionUser.accountId !== auditInfo.aid) {
@@ -964,6 +1276,9 @@ module.exports = app => {
                 const responseData = {
                     err: 0, msg: '', data: {},
                 };
+                if (!ctx.financialPay.fpsid || !ctx.financialPay.payStage) {
+                    throw '未关联资金支付单位期无法进行任何操作';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data.type) {
                     throw '提交数据错误';
@@ -994,13 +1309,13 @@ module.exports = app => {
                         responseData.data = await ctx.service.financialPayContract.addContracts(ctx.financialPay, data.contract_ids);
                         break;
                     case 'contract_del':
-                        responseData.data = await ctx.service.financialPayContract.delContract(ctx.financialPay.id, data.ids);
+                        responseData.data = await ctx.service.financialPayContract.delContract(ctx.financialPay.id, ctx.financialPay.payStage, data.ids);
                         break;
                     case 'contract_update':
-                        responseData.data = await ctx.service.financialPayContract.updateContract(ctx.financialPay.id, data.updateData);
+                        responseData.data = await ctx.service.financialPayContract.updateContract(ctx.financialPay.id, ctx.financialPay.payStage, data.updateData);
                         break;
                     case 'contract_paste':
-                        responseData.data = await ctx.service.financialPayContract.updateContracts(ctx.financialPay.id, data.updateData);
+                        responseData.data = await ctx.service.financialPayContract.updateContracts(ctx.financialPay.id, ctx.financialPay.payStage, data.updateData);
                         break;
                     case 'file_bill':
                         responseData.data = await ctx.service.financialPayAtt.updateBill(data.id, data.bill);
@@ -1024,6 +1339,9 @@ module.exports = app => {
                 const parts = this.ctx.multipart({
                     autoFields: true,
                 });
+                if (!ctx.financialPay.fpsid || !ctx.financialPay.payStage) {
+                    throw '未关联资金支付单位期无法进行任何操作';
+                }
                 const files = [];
                 const create_time = Date.parse(new Date()) / 1000;
                 let idx = 0;
@@ -1083,6 +1401,9 @@ module.exports = app => {
          */
         async payDeleteFile(ctx) {
             try {
+                if (!ctx.financialPay.fpsid || !ctx.financialPay.payStage) {
+                    throw '未关联资金支付单位期无法进行任何操作';
+                }
                 const { fpcid, id } = JSON.parse(ctx.request.body.data);
                 const fileInfo = await ctx.service.financialPayAtt.getDataById(id);
                 if (fileInfo || Object.keys(fileInfo).length) {
@@ -1184,7 +1505,7 @@ module.exports = app => {
                 responseData.data.tenders = hadTender ? await ctx.service.tender.getAllDataByCondition({ where: tenderCondition, columns: ['id', 'name'] }) : [];
                 responseData.data.transferList = tid === null ? await ctx.service.financialTransfer.getAllDataByCondition({ where: { spid: ctx.subProject.id } }) : [];
                 responseData.data.transferTenderList = tid === null ? await ctx.service.financialTransferTender.getAllDataByCondition({ where: { spid: ctx.subProject.id } }) : [];
-                responseData.data.payList = await ctx.service.financialPay.getListByStatus(ctx.subProject.id, 0, filterTids);
+                responseData.data.payList = await ctx.service.financialPay.getListByStatus(ctx.subProject.id, null, 0, filterTids);
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);

+ 1 - 1
app/controller/ledger_controller.js

@@ -706,7 +706,7 @@ module.exports = app => {
          */
         async bwtz(ctx) {
             try {
-                const sjsRela = await ctx.service.project.getTenderSjsRela(ctx.session.sessionProject.id, ctx.tender.info.display.exMemo);
+                const sjsRela = require('../const/setting').sjsRela;// await ctx.service.project.getTenderSjsRela(ctx.session.sessionProject.id, ctx.tender.info.display.exMemo);
                 const renderData = {
                     tender: ctx.tender.data,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.bwtz),

+ 3 - 1
app/controller/measure_controller.js

@@ -110,8 +110,10 @@ module.exports = app => {
                 }
                 const stages = await ctx.service.stage.getAllDataByCondition({ where: { tid: ctx.tender.id }});
                 const unCompleteStageCount = stages.filter(s => { return s.status !== auditConst.status.checked; }).length;
-                if (unCompleteStageCount.length > 0) {
+                if (unCompleteStageCount > 0) {
                     if (ctx.subProject.page_show.openMultiStageCalc) {
+                        if (ctx.tender.info.calc_type === 'tp') throw `【总价合同】不允许新增下一期,请联系管理员`;
+                        if (ctx.subProject.page_show.correctCalcContractTp) throw `【反算本期合同计量】不允许新增下一期,请联系管理员`;
                         if (unCompleteStageCount >= ctx.session.sessionProject.page_show.maxMultiStageCount)
                             throw `最多只可同时进行${ctx.session.sessionProject.page_show.maxMultiStageCount}期计量`;
                     } else {

+ 407 - 0
app/controller/quality_controller.js

@@ -0,0 +1,407 @@
+'use strict';
+
+/**
+ * 标段管理控制器
+ *
+ * @author Mai
+ * @date 2025/7/17
+ * @version
+ */
+
+const auditConst = require('../const/audit');
+const contractConst = require('../const/contract');
+const moment = require('moment');
+const sendToWormhole = require('stream-wormhole');
+const fs = require('fs');
+const path = require('path');
+
+module.exports = app => {
+    class QualityController extends app.BaseController {
+        constructor(ctx) {
+            super(ctx);
+            ctx.showProject = true;
+            // ctx.showTitle = true;
+        }
+
+        async tender(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.tender),
+                };
+                const accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                    where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                    columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
+                });
+                renderData.accountList = accountList;
+                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
+                renderData.accountGroup = unitList.map(item => {
+                    const groupList = accountList.filter(item1 => item1.company === item.name);
+                    return { groupName: item.name, groupList };
+                });
+                renderData.accountInfo = await this.ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                renderData.tenderList = await ctx.service.tender.getSpecList(ctx.service.tenderPermission, 'quality', ctx.session.sessionUser.is_admin ? 'all' : '');
+                renderData.categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
+                // renderData.selfCategoryLevel = this.ctx.subProject.permission.self_category_level;
+                renderData.permissionConst = ctx.service.tenderPermission.partPermissionConst('quality');
+                renderData.permissionBlock = ctx.service.tenderPermission.partPermissionBlock('quality');
+                await this.layout('quality/tender.ejs', renderData, 'quality/tender_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect('/dashboard');
+            }
+        }
+
+        async member(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.tenderPermission.getPartsPermission(data.tid, data.parts);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '查询标段权限错误');
+            }
+        }
+
+        async memberSave(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                await ctx.service.tenderPermission.savePermission(data.tid, data.member, data.permissionBlock);
+                ctx.body = { err: 0, msg: '', data: '' };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存标段权限错误');
+            }
+        }
+
+        async info(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const renderData = {
+                    thirdParty: {
+                        gxby: ctx.session.sessionProject.gxby_status,
+                        dagl: ctx.session.sessionProject.dagl_status,
+                    },
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.info),
+                };
+                await this.layout('quality/info.ejs', renderData, 'quality/info_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect('/quality');
+            }
+        }
+
+        async flaw(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.flaw),
+                };
+                await this.layout('quality/flaw.ejs', renderData, 'quality/flaw_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect('/quality');
+            }
+        }
+
+        async lab(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.lab),
+                };
+                await this.layout('quality/lab.ejs', renderData, 'quality/lab_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect('/quality');
+            }
+        }
+
+        async _loadXmjData(spec) {
+            const xmj = await this.ctx.service.ledger.getAllDataByCondition({
+                where: { tender_id: this.ctx.tender.id },
+                columns: ['id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf', 'code', 'name'],
+            });
+            const quality = spec && spec.loadStatus ? await this.ctx.service.quality.getAllDataByCondition({
+                where: { tid: this.ctx.tender.id, rela_type: 'xmj' },
+                columns: ['rela_id', 'gxby_status', 'gxby_date', 'dagl_status'],
+            }) : [];
+            this.ctx.helper.assignRelaData(xmj, [
+                { data: quality, fields: ['gxby_status', 'gxby_date', 'dagl_status'], prefix: '', relaId: 'rela_id' },
+            ]);
+            return xmj;
+        }
+        async _loadPosData(condition, spec) {
+            const pos = await this.ctx.service.pos.getAllDataByCondition({
+                where: condition,
+                columns: ['id', 'tid', 'lid', 'tree_id', 'tree_pid', 'level', 'order', 'full_path', 'is_leaf', 'code', 'b_code', 'name'],
+            });
+            const quality = spec && spec.loadStatus ? await this.ctx.service.quality.getAllDataByCondition({
+                where: { tid: this.ctx.tender.id, rela_type: 'pos' },
+                columns: ['rela_id', 'gxby_status', 'gxby_date', 'dagl_status'],
+            }) : [];
+            this.ctx.helper.assignRelaData(pos, [
+                { data: quality, fields: ['gxby_status', 'gxby_date', 'dagl_status'], prefix: '', relaId: 'rela_id' },
+            ]);
+            return pos;
+        }
+        async _loadDetailData(id) {
+            const result = await this.ctx.service.quality.getDataByCondition({ tid: this.ctx.tender.id, rela_id: id });
+            if (!result) return result;
+            await this.ctx.service.quality.loadQualityDetail(result);
+            await this.ctx.service.quality.checkQualityStatusAndSave(result);
+            return result;
+        }
+
+        async _loadData(type, data, result) {
+            if (result[type]) return result[type];
+            switch (type) {
+                case 'xmj':
+                    return this._loadXmjData(data.spec);
+                case 'pos':
+                    return this._loadPosData({ tid: this.ctx.tender.id }, data.spec);
+                case 'pos1':
+                    if (result.pos) return result.pos.filter(p => { return p.level === 1; });
+                    return this._loadPosData({ tid: this.ctx.tender.id, level: 1 }, data.spec);
+                case 'detail':
+                    if (!data.id) throw '缺少参数';
+                    return this._loadDetailData(data.id);
+                case 'quality':
+                    return await this.ctx.service.quality.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+                case 'lab':
+                    return await this.ctx.service.qualityLab.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+                default: throw '未知数据类型';
+            }
+        }
+        async load(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.filter) throw '参数错误';
+                const filter = data.filter.split(';');
+                const result = {};
+                for (const f of filter) {
+                    result[f] = await this._loadData(f, data, result);
+                }
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取数据错误');
+            }
+        }
+
+        async _saveKaigong(ctx, data) {
+            if (data.add) {
+                if (!ctx.permission.quality.add) throw '您无权新增开工';
+                await ctx.service.quality.kaigong(data, data.add.name, data.add.date);
+            } else if (data.update) {
+                await ctx.service.quality.kaigong(data, data.update.name, data.update.date);
+            } else if (data.del) {
+                await ctx.service.quality.delKaigong(data);
+            }
+        }
+        async _saveGongxu(ctx, data) {
+            if (data.add) {
+                if (!ctx.permission.quality.add) throw '您无权新增工序';
+                await ctx.service.qualityGongxu.add(data, data.add.name);
+            } else if (data.update) {
+                await ctx.service.qualityGongxu.update(data.update.id, data.update.name);
+            } else if (data.del) {
+                await ctx.service.qualityGongxu.del(data.del);
+            }
+        }
+        async _saveYinbi(ctx, data) {
+            if (data.add) {
+                if (!ctx.permission.quality.add) throw '您无权新增隐蔽工程';
+                await ctx.service.qualityYinbi.add(data, data.add);
+            } else if (data.update) {
+                await ctx.service.qualityYinbi.update(data.update.id, data.update);
+            } else if (data.del) {
+                await ctx.service.qualityYinbi.del(data.del);
+            }
+        }
+        async save(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.quality_id && (!data.rela_type && !data.rela_id)) throw '参数错误';
+
+                switch (ctx.params.type) {
+                    case 'kaigong':
+                        await this._saveKaigong(ctx, data);
+                        break;
+                    case 'gongxu':
+                        await this._saveGongxu(ctx, data);
+                        break;
+                    case 'yinbi':
+                        await this._saveYinbi(ctx, data);
+                        break;
+                    default: throw '请求错误';
+                }
+                const quality = await this._loadDetailData(data.rela_id);
+                ctx.body = { err: 0, msg: '', data: quality };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存数据错误');
+            }
+        }
+
+        async checkCanUpload(ctx) {
+            if (!ctx.permission.quality.upload) {
+                throw '您无权上传、删除文件';
+            }
+        }
+        async uploadFile(ctx) {
+            let stream;
+            try {
+                await this.checkCanUpload(ctx);
+
+                const parts = ctx.multipart({ autoFields: true });
+
+                let index = 0;
+                const create_time = Date.parse(new Date()) / 1000;
+                let stream = await parts();
+                const user = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const quality = await ctx.service.quality.getDataById(parts.field.quality_id);
+                if (!quality) throw '工程未开工';
+                const blockType = parts.field.block_type;
+                const blockId = parts.field.block_id || '';
+                const specType = parts.field.spec_type || '';
+                if (!blockType) throw '参数错误';
+
+                const uploadfiles = [];
+                while (stream !== undefined) {
+                    if (!stream.filename) throw '未发现上传文件!';
+
+                    const fileInfo = path.parse(stream.filename);
+                    const filepath = `${ctx.session.sessionProject.id}/${ctx.tender.id}/quality/${blockType}/${ctx.moment().format('YYYYMMDD')}/${create_time + '_' + index + fileInfo.ext}`;
+
+                    // 保存文件
+                    await ctx.app.oss.put(filepath, stream);
+                    await sendToWormhole(stream);
+
+                    // 插入到stage_pay对应的附件列表中
+                    uploadfiles.push({
+                        filename: fileInfo.name,
+                        fileext: fileInfo.ext,
+                        filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
+                        filepath,
+                    });
+                    ++index;
+                    if (Array.isArray(parts.field.size) && index < parts.field.size.length) {
+                        stream = await parts();
+                    } else {
+                        stream = undefined;
+                    }
+                }
+
+                const result = await ctx.service.qualityFile.addFiles(user, quality, uploadfiles, blockType, blockId, specType);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (error) {
+                ctx.log(error);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) await sendToWormhole(stream);
+                ctx.body = this.ajaxErrorBody(error, '上传附件失败,请重试');
+            }
+        }
+        async delFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.del) throw '缺少参数';
+                const result = await ctx.service.qualityFile.delFiles(data.del);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (error) {
+                this.log(error);
+                ctx.ajaxErrorBody(error, '删除附件失败');
+            }
+        }
+        async saveFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.id) throw '缺少参数';
+                const result = await ctx.service.file.qualityFile(data.id, data.filename);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (error) {
+                this.log(error);
+                ctx.ajaxErrorBody(error, '编辑附件失败');
+            }
+        }
+
+        async rule(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const ruleGroups = await ctx.service.qualityRule.getRuleGroups(ctx.session.sessionProject.id);
+                const tenderList = await ctx.service.tender.getAllDataByCondition({ where: { project_id: ctx.session.sessionProject.id } });
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.rule),
+                    ruleGroups,
+                    thirdParty: {
+                        gxby: ctx.session.sessionProject.gxby_status,
+                        dagl: ctx.session.sessionProject.dagl_status,
+                    },
+                    tenderList,
+                };
+                await this.layout('quality/rule.ejs', renderData, 'quality/rule_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect('/quality');
+            }
+        }
+        async ruleSave(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.group && !data.rule && !data.quality) throw '参数错误';
+                if (data.group) {
+                    const result = await ctx.service.qualityRule.saveGroup(data.group);
+                    ctx.body = { err: 0, msg: '', data: result };
+                } else if (data.rule) {
+                    const result = await ctx.service.qualityRule.saveRule(data.rule);
+                    ctx.body = { err: 0, msg: '', data: result };
+                } else if (data.quality) {
+                    const result = await ctx.service.quality.saveQualityManage(this.ctx.tender.id, data.quality);
+                    ctx.body = { err: 0, msg: '', data: result };
+                }
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存状态推送规则错误');
+            }
+        }
+
+        async _pushIncStatus(select) {
+            // todo 向其他系统推送
+            return await this.ctx.service.quality.pushIncStatus(select);
+        }
+        async _pushAllStatus(select) {
+            // todo 向其他系统推送
+            return await this.ctx.service.quality.pushAllStatus(select);
+        }
+        async pushStatus(ctx) {
+            try {
+                if (!ctx.session.sessionProject.page_show.quality) throw '该功能已关闭';
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.push_type) throw '参数错误';
+                let count;
+                switch (data.push_type) {
+                    case 'inc':
+                        count = await this._pushIncStatus(data.select);
+                        break;
+                    case 'all':
+                        count = await this._pushAllStatus();
+                        break;
+                    default: throw '参数错误';
+                }
+                ctx.body = { err: 0, msg: '', data: count };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '推送失败');
+            }
+        }
+    }
+
+    return QualityController;
+};

+ 1 - 1
app/controller/stage_controller.js

@@ -1648,7 +1648,7 @@ module.exports = app => {
                 await this._getStageAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.bwtz);
-                const sjsRela = await this.ctx.service.project.getTenderSjsRela(ctx.session.sessionProject.id, ctx.tender.info.display.exMemo);
+                const sjsRela = require('../const/setting').sjsRela;// await this.ctx.service.project.getTenderSjsRela(ctx.session.sessionProject.id, ctx.tender.info.display.exMemo);
                 renderData.ex_memo1 = sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo1'; });
                 renderData.ex_memo2 = sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo2'; });
                 renderData.ex_memo3 = sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo3'; });

+ 59 - 0
app/controller/wap_controller.js

@@ -19,6 +19,9 @@ const path = require('path');
 const sendToWormhole = require('stream-wormhole');
 const moment = require('moment');
 const auditType = require('../const/audit').auditType;
+const AiInspect = require('../lib/ai_inspect');
+const uuid = require('node-uuid');
+const nlsToken = require('../lib/nls_token');
 
 module.exports = app => {
 
@@ -1176,6 +1179,62 @@ module.exports = app => {
                 ctx.redirect('/wap/dashboard');
             }
         }
+
+        async inspection(ctx) {
+            const renderData = {
+            };
+            await ctx.render('wap/inspection.ejs', renderData);
+        }
+
+        async inspectionAiAsk(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: '',
+            };
+            try {
+                const { user_input, conversationId } = JSON.parse(ctx.request.body.data);
+                if (!user_input) {
+                    throw '参数有误';
+                }
+                const ai = new AiInspect({
+                    chatflowId: conversationId,
+                });
+                const result = await ai.ask(user_input);
+                let answerText = result.answer || '';
+                let jsonPart = null;
+                const jsonMatch = answerText.match(/\{[\s\S]*\}/);
+                if (jsonMatch) {
+                    jsonPart = JSON.parse(jsonMatch[0]);
+                    answerText = answerText.replace(jsonMatch[0], '').trim();
+                }
+                responseData.data = {
+                    answer_text: answerText,
+                    answer_json: jsonPart,
+                    conversation_id: result.conversation_id || '',
+                };
+            } catch (error) {
+                responseData.err = 1;
+                responseData.msg = error.message ? error.message : error;
+            }
+            ctx.body = responseData;
+        }
+
+        async voiceToken(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: '',
+            };
+            try {
+                const token = await nlsToken.getToken();
+                responseData.data = token;
+            } catch (error) {
+                responseData.err = 1;
+                responseData.msg = error.message ? error.message : error;
+            }
+            ctx.body = responseData;
+        }
     }
 
     return WapController;

+ 53 - 0
app/lib/ai_inspect.js

@@ -0,0 +1,53 @@
+'use strict';
+const axios = require('axios');
+
+class AiInspect {
+    /**
+     * 构造函数
+     * @param {object} config
+     * @param {string} config.apiKey - Dify API Key
+     * @param {string} config.chatflowId - ChatFlow ID
+     * @param {string} [config.baseUrl=https://api.dify.ai/v1] - Dify API 接口地址
+     */
+    constructor({ apiKey = 'app-vc7cLDuIRJngvEgQUMBqT3SX', chatflowId = '', baseUrl = 'http://workflow.smartcost.com.cn/v1' }) {
+        if (!apiKey) {
+            throw new Error('apiKey 不能为空');
+        }
+
+        this.apiKey = apiKey;
+        this.chatflowId = chatflowId;
+        this.baseUrl = baseUrl;
+    }
+
+    /**
+     * 向 ChatFlow 发送请求
+     * @param {string} userInput - 用户输入内容
+     * @param {string} [userId='user_123'] - 用户 ID
+     * @returns {Promise<object>} - Dify 返回的对象
+     */
+    async ask(userInput, userId = 'user_123') {
+        const url = `${this.baseUrl}/chat-messages`;
+        const headers = {
+            'Authorization': `Bearer ${this.apiKey}`,
+            'Content-Type': 'application/json',
+        };
+
+        const body = {
+            inputs: {}, // 如果你的 ChatFlow 有 schema,可填写 key-value
+            query: userInput,
+            response_mode: 'blocking',
+            user: userId,
+            conversation_id: this.chatflowId,
+        };
+
+        try {
+            const res = await axios.post(url, body, { headers });
+            return res.data;
+        } catch (err) {
+            console.error('Dify 请求失败:', err.response || err.response.data || err.message);
+            throw new Error('Dify 请求失败');
+        }
+    }
+}
+
+module.exports = AiInspect;

+ 41 - 0
app/lib/nls_token.js

@@ -0,0 +1,41 @@
+'use strict';
+
+/**
+ * 阿里云获取语音token接口
+ *
+ * @author CaiAoLin
+ * @date 2018/1/25
+ * @version
+ */
+
+const Core = require('@alicloud/pop-core');
+const smsAli = require('../const/sms_alitemplate.js');
+class NlsToken {
+
+    async getToken() {
+        const client = new Core({
+            accessKeyId: smsAli.accessKey,
+            accessKeySecret: smsAli.accessKeySecret,
+            endpoint: 'https://nls-meta.cn-shanghai.aliyuncs.com', // 注意语音服务用这个endpoint
+            apiVersion: '2019-02-28',
+        });
+
+        const params = {
+            Action: 'CreateToken',
+        };
+
+        const requestOption = {
+            method: 'POST',
+        };
+
+        try {
+            const result = await client.request('CreateToken', params, requestOption);
+            return result.Token;
+        } catch (err) {
+            this.ctx.logger.error('获取语音识别Token失败', err);
+            throw err;
+        }
+    }
+}
+
+module.exports = NlsToken;

+ 36 - 12
app/lib/revise_price.js

@@ -61,6 +61,15 @@ class revisePriceCalc {
         return p;
     }
 
+    _calcContractTpByTp(bills, decimal) {
+        let activeQty = this.ctx.helper.add(bills.quantity, bills.cur_qc_minus_qty);
+        let end_contract_qty = bills.cur_contract_qty;
+        activeQty = this.ctx.helper.add(activeQty, bills.pre_qc_minus_qty);
+        end_contract_qty = this.ctx.helper.add(end_contract_qty, bills.pre_contract_qty);
+        const end_contract_tp = this.ctx.helper.mul(this.ctx.helper.div(end_contract_qty, activeQty), bills.total_price, decimal.tp);
+        return this.ctx.helper.sub(end_contract_tp, bills.pre_contract_tp);
+    }
+
     /**
      * 新增一期计量,检查单价调整
      * @param {Object} newStage - 新计量期
@@ -79,7 +88,7 @@ class revisePriceCalc {
         // 加载树结构
         const bills = await this.ctx.service.ledger.getData(newStage.tid);
         this.ctx.helper.assignRelaData(bills, [
-            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price', 'positive_qc_qty', 'negative_qc_qty', 'positive_qc_tp', 'negative_qc_tp'], prefix: 'pre_', relaId: 'lid' },
+            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price', 'positive_qc_qty', 'negative_qc_qty', 'positive_qc_tp', 'negative_qc_tp', 'qc_minus_qty'], prefix: 'pre_', relaId: 'lid' },
         ]);
         const billsTree = new Ledger.billsTree(this.ctx, { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: [] });
         billsTree.loadDatas(bills);
@@ -88,13 +97,16 @@ class revisePriceCalc {
         const result = { ibData: [] };
         const helper = this.ctx.helper;
         const decimal = this.ctx.tender.info.decimal;
+        const calcType = this.ctx.tender.info.calc_type;
         billsTree.calculateAll(node => {
             if (!node.pre_id) return;
             if (!node.pre_contract_qty && !node.pre_qc_qty) return;
             if (node.children && node.children.length > 0) return;
             const priceDiff = helper.sub(node.unit_price, node.pre_unit_price);
             if (!priceDiff) return;
-            node.contract_pc_tp = helper.sub(helper.mul(node.pre_contract_qty, node.unit_price, decimal.tp), node.pre_contract_tp);
+            node.contract_pc_tp = calcType === 'up'
+                ? helper.sub(helper.mul(node.pre_contract_qty, node.unit_price, decimal.tp), node.pre_contract_tp)
+                : 0;
             node.qc_pc_tp = helper.sub(helper.mul(node.pre_qc_qty, node.unit_price, decimal.tp), node.pre_qc_tp);
             node.pc_tp = helper.add(node.contract_pc_tp, node.qc_pc_tp);
             node.positive_qc_pc_tp = helper.sub(helper.mul(node.pre_positive_qc_qty, node.unit_price, decimal.tp), node.pre_positive_qc_tp);
@@ -137,8 +149,8 @@ class revisePriceCalc {
         // 加载树结构
         const bills = await this.ctx.service.ledger.getData(stage.tid);
         this.ctx.helper.assignRelaData(bills, [
-            { data: curBillsData, fields: ['id', 'contract_qty', 'qc_qty', 'positive_qc_qty', 'negative_qc_qty', 'postil', 'times', 'order', 'ex_stage_qty1'], prefix: 'cur_', relaId: 'lid' },
-            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price', 'positive_qc_qty', 'negative_qc_qty', 'positive_qc_tp', 'negative_qc_tp'], prefix: 'pre_', relaId: 'lid' },
+            { data: curBillsData, fields: ['id', 'contract_qty', 'qc_qty', 'positive_qc_qty', 'negative_qc_qty', 'postil', 'times', 'order', 'ex_stage_qty1', 'qc_minus_qty'], prefix: 'cur_', relaId: 'lid' },
+            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price', 'positive_qc_qty', 'negative_qc_qty', 'positive_qc_tp', 'negative_qc_tp', 'qc_minus_qty'], prefix: 'pre_', relaId: 'lid' },
         ]);
         const billsTree = new Ledger.billsTree(this.ctx, { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: [] });
         billsTree.loadDatas(bills);
@@ -150,12 +162,16 @@ class revisePriceCalc {
         const helper = this.ctx.helper;
         const decimal = this.ctx.tender.info.decimal;
         const said = this.ctx.session.sessionUser.accountId;
+        const calcType = this.ctx.tender.info.calc_type;
+        const self = this;
         billsTree.calculateAll(node => {
             if (node.children && node.children.length > 0) return;
             // const priceDiff = helper.sub(node.unit_price, node.pre_unit_price);
             // if (!priceDiff) return;
             if (node.cur_id && (node.cur_contract_qty || node.cur_qc_qty || node.cur_ex_stage_qty1)) {
-                const cur_contract_tp = helper.mul(node.cur_contract_qty, node.unit_price, decimal.tp);
+                const cur_contract_tp = calcType === 'up'
+                    ? helper.mul(node.cur_contract_qty, node.unit_price, decimal.tp)
+                    : self._calcContractTpByTp(node, decimal);
                 const cur_qc_tp = helper.mul(node.cur_qc_qty, node.unit_price, decimal.tp);
                 const cur_positive_qc_tp = helper.mul(node.cur_positive_qc_qty, node.unit_price, decimal.tp);
                 const cur_negative_qc_tp = helper.mul(node.cur_negative_qc_qty, node.unit_price, decimal.tp);
@@ -175,7 +191,7 @@ class revisePriceCalc {
                 }
             }
             if (node.pre_id && (node.pre_contract_qty || node.pre_qc_qty)) {
-                const contract_pc_tp = helper.sub(helper.mul(node.pre_contract_qty, node.unit_price, decimal.tp), node.pre_contract_tp);
+                const contract_pc_tp = calcType === 'up' ? helper.sub(helper.mul(node.pre_contract_qty, node.unit_price, decimal.tp), node.pre_contract_tp) : 0;
                 const qc_pc_tp = helper.sub(helper.mul(node.pre_qc_qty, node.unit_price, decimal.tp), node.pre_qc_tp);
                 const pc_tp = helper.add(contract_pc_tp, qc_pc_tp);
                 const positive_qc_pc_tp = helper.sub(helper.mul(node.pre_positive_qc_qty, node.unit_price, decimal.tp), node.pre_positive_qc_tp);
@@ -367,8 +383,8 @@ class revisePriceCalc {
 
         // 加载树结构
         this.ctx.helper.assignRelaData(bills, [
-            { data: curBillsData, fields: ['id', 'contract_qty', 'qc_qty', 'positive_qc_qty', 'negative_qc_qty', 'times', 'order', 'postil', 'ex_stage_qty1', 'ex_stage_tp1'], prefix: 'cur_', relaId: 'lid' },
-            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price', 'positive_qc_qty', 'negative_qc_qty', 'positive_qc_tp', 'negative_qc_tp'], prefix: 'pre_', relaId: 'lid' },
+            { data: curBillsData, fields: ['id', 'contract_qty', 'qc_qty', 'positive_qc_qty', 'negative_qc_qty', 'times', 'order', 'postil', 'ex_stage_qty1', 'ex_stage_tp1', 'qc_minus_qty'], prefix: 'cur_', relaId: 'lid' },
+            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price', 'positive_qc_qty', 'negative_qc_qty', 'positive_qc_tp', 'negative_qc_tp', 'qc_minus_qty'], prefix: 'pre_', relaId: 'lid' },
         ], 'id', true);
         const billsTree = new Ledger.billsTree(this.ctx, { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: [] });
         billsTree.loadDatas(bills);
@@ -379,10 +395,14 @@ class revisePriceCalc {
         const helper = this.ctx.helper;
         const decimal = this.ctx.tender.info.decimal;
         const said = this.ctx.session.sessionUser.accountId;
+        const calcType = this.ctx.tender.info.calc_type;
+        const self = this;
         billsTree.calculateAll(node => {
             if (node.children && node.children.length > 0) return;
             if (node.cur_id && (node.cur_contract_qty || node.cur_qc_qty || node.cur_ex_stage_qty1)) {
-                const cur_contract_tp = helper.mul(node.cur_contract_qty, node.unit_price, decimal.tp);
+                const cur_contract_tp = calcType === 'up'
+                    ? helper.mul(node.cur_contract_qty, node.unit_price, decimal.tp)
+                    : self._calcContractTpByTp(node, decimal);
                 const cur_qc_tp = helper.mul(node.cur_qc_qty, node.unit_price, decimal.tp);
                 const cur_positive_qc_tp = helper.mul(node.cur_positive_qc_qty, node.unit_price, decimal.tp);
                 const cur_negative_qc_tp = helper.mul(node.cur_negative_qc_qty, node.unit_price, decimal.tp);
@@ -416,7 +436,7 @@ class revisePriceCalc {
             const priceDiff = helper.sub(node.unit_price, node.pre_unit_price);
             if (!priceDiff) return;
             if (node.pre_id && (node.pre_contract_qty || node.pre_qc_qty)) {
-                node.contract_pc_tp = helper.sub(helper.mul(node.pre_contract_qty, node.unit_price, decimal.tp), node.pre_contract_tp);
+                node.contract_pc_tp = calcType === 'up' ? helper.sub(helper.mul(node.pre_contract_qty, node.unit_price, decimal.tp), node.pre_contract_tp) : 0;
                 node.qc_pc_tp = helper.sub(helper.mul(node.pre_qc_qty, node.unit_price, decimal.tp), node.pre_qc_tp);
                 node.pc_tp = helper.add(node.contract_pc_tp, node.qc_pc_tp);
                 node.positive_qc_pc_tp = helper.sub(helper.mul(node.pre_positive_qc_qty, node.unit_price, decimal.tp), node.pre_positive_qc_tp);
@@ -453,7 +473,7 @@ class revisePriceCalc {
 
         // 加载树结构
         this.ctx.helper.assignRelaData(bills, [
-            { data: curBillsData, fields: ['id', 'contract_qty', 'qc_qty', 'positive_qc_qty', 'negative_qc_qty', 'times', 'order', 'postil'], prefix: 'cur_', relaId: 'lid' },
+            { data: curBillsData, fields: ['id', 'contract_qty', 'qc_qty', 'positive_qc_qty', 'negative_qc_qty', 'times', 'order', 'postil', 'qc_minus_qty'], prefix: 'cur_', relaId: 'lid' },
         ], 'id', true);
         const billsTree = new Ledger.billsTree(this.ctx, { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: [] });
         billsTree.loadDatas(bills);
@@ -464,10 +484,14 @@ class revisePriceCalc {
         const helper = this.ctx.helper;
         const decimal = this.ctx.tender.info.decimal;
         const said = this.ctx.session.sessionUser.accountId;
+        const calcType = this.ctx.tender.info.calc_type;
+        const self = this;
         billsTree.calculateAll(node => {
             if (node.children && node.children.length > 0) return;
             if (node.cur_id && (node.cur_contract_qty || node.cur_qc_qty)) {
-                const cur_contract_tp = helper.mul(node.cur_contract_qty, node.unit_price, decimal.tp);
+                const cur_contract_tp = calcType === 'up'
+                    ? helper.mul(node.cur_contract_qty, node.unit_price, decimal.tp)
+                    : self._calcContractTpByTp(node, decimal);
                 const cur_qc_tp = helper.mul(node.cur_qc_qty, node.unit_price, decimal.tp);
                 const cur_positive_qc_tp = helper.mul(node.cur_positive_qc_qty, node.unit_price, decimal.tp);
                 const cur_negative_qc_tp = helper.mul(node.cur_negative_qc_qty, node.unit_price, decimal.tp);

+ 1 - 1
app/middleware/tender_check.js

@@ -25,7 +25,7 @@ module.exports = options => {
     return function* tenderCheck(next) {
         try {
             // 读取标段数据
-            const tender = { id: parseInt(this.params.id) };
+            const tender = this.subProject ? { id: parseInt(this.params.tid) } : { id: parseInt(this.params.id) };
             if (!tender.id) {
                 throw '当前未打开标段';
             }

+ 40 - 0
app/middleware/tender_permission_check.js

@@ -0,0 +1,40 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* tenderPermissionCheck(next) {
+        try {
+            // 读取标段数据
+
+            if (this.session.sessionUser.is_admin) {
+                this.permission = this.service.tenderPermission.getAdminPermission();
+            } else {
+                this.permission = yield this.service.tenderPermission.getUserPermission(this.tender.id, this.session.sessionUser.accountId);
+            }
+            yield next;
+        } catch (err) {
+            this.log(err);
+            if (this.helper.isAjax(this.request)) {
+                this.ajaxErrorBody(err, '未知错误');
+            } else {
+                this.postError(err, '未知错误');
+                this.redirect(this.request.headers.referer);
+            }
+        }
+    };
+};

BIN
app/public/css/ztree/img/diy/10.png


BIN
app/public/css/ztree/img/zTreeStandard.png


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

@@ -1516,11 +1516,11 @@ $(document).ready(() => {
                 for (let iCol = 0; iCol < range.colCount; iCol++) {
                     const curCol = range.col + iCol;
                     let colSetting = info.sheet.zh_setting.cols[curCol];
-                    if ((needColAdd && curCol > 2) || (needCopyIgnore && curCol === 2)) { // 要判断是否已隐藏了变更详情,是则变更详情后面的curCol要+1
-                        const newCurCol = curCol + 1;
-                        needColAdd = true;
-                        colSetting = info.sheet.zh_setting.cols[newCurCol];
-                    }
+                    // if ((needColAdd && curCol > 2) || (needCopyIgnore && curCol === 2)) { // 要判断是否已隐藏了变更详情,是则变更详情后面的curCol要+1
+                    //     const newCurCol = curCol + 1;
+                    //     needColAdd = true;
+                    //     colSetting = info.sheet.zh_setting.cols[newCurCol];
+                    // }
                     if (!colSetting) continue;
 
                     let validText = info.sheet.getText(curRow, curCol);
@@ -2194,11 +2194,11 @@ $(document).ready(() => {
                 for (let iCol = 0; iCol < range.colCount; iCol++) {
                     const curCol = range.col + iCol;
                     let colSetting = info.sheet.zh_setting.cols[curCol];
-                    if ((needColAdd && curCol > 2) || (needCopyIgnore && curCol === 2)) { // 要判断是否已隐藏了变更详情,是则变更详情后面的curCol要+1
-                        const newCurCol = curCol + 1;
-                        needColAdd = true;
-                        colSetting = info.sheet.zh_setting.cols[newCurCol];
-                    }
+                    // if ((needColAdd && curCol > 2) || (needCopyIgnore && curCol === 2)) { // 要判断是否已隐藏了变更详情,是则变更详情后面的curCol要+1
+                    //     const newCurCol = curCol + 1;
+                    //     needColAdd = true;
+                    //     colSetting = info.sheet.zh_setting.cols[newCurCol];
+                    // }
                     if (!colSetting) continue;
 
                     let validText = info.sheet.getText(curRow, curCol);
@@ -4181,11 +4181,11 @@ $(document).ready(() => {
                 for (let iCol = 0; iCol < range.colCount; iCol++) {
                     const curCol = range.col + iCol;
                     let colSetting = info.sheet.zh_setting.cols[curCol];
-                    if ((needColAdd && curCol > 2) || (needCopyIgnore && curCol === 2)) { // 要判断是否已隐藏了变更详情,是则变更详情后面的curCol要+1
-                        const newCurCol = curCol + 1;
-                        needColAdd = true;
-                        colSetting = info.sheet.zh_setting.cols[newCurCol];
-                    }
+                    // if ((needColAdd && curCol > 2) || (needCopyIgnore && curCol === 2)) { // 要判断是否已隐藏了变更详情,是则变更详情后面的curCol要+1
+                    //     const newCurCol = curCol + 1;
+                    //     needColAdd = true;
+                    //     colSetting = info.sheet.zh_setting.cols[newCurCol];
+                    // }
                     if (!colSetting) continue;
 
                     let validText = info.sheet.getText(curRow, curCol);

+ 150 - 32
app/public/js/change_revise.js

@@ -932,22 +932,49 @@ $(document).ready(() => {
                     node[col.field] = cInfo[col.field];
                     delete cInfo.waitingLoading;
                     if (cInfo.id) {
-                        postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', {
-                            type: 'update',
-                            updateData: cInfo
-                        }, function (result) {
-                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                            const billsNode = node;
-                            billsTreeSpreadObj.reCalcCamount(billsNode);
-                            const loadResult = {update: [billsNode]};
-                            const refreshNode = billsTree.loadPostData(loadResult);
-                            billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
-                        }, function () {
-                            cInfo[col.field] = orgValue;
-                            cInfo.spamount = orgValue;
-                            node[col.field] = orgValue;
-                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        });
+                        if (cInfo.camount === 0 && cInfo.camount_expr === '') {
+                            if (changeOrder && _.findIndex(oldChangeList, {id: cInfo.id}) !== -1) {
+                                toastr.warning('插入台账清单功能下无法从这移除已勾选清单');
+                                return
+                            }
+                            if (_.find(changeUsedData, {cbid: cInfo.id})) {
+                                toastr.warning('清单计量单元已被使用,无法取消勾选');
+                                return
+                            } else if (checkIsSettle(cInfo)) {
+                                toastr.warning('清单计量单元已结算,无法取消勾选');
+                                info.sheet.setValue(info.row, info.col, 1);
+                                return
+                            }
+                            postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', {type: 'del-change-list', ids: [cInfo.id] }, function (result) {
+                                changeList = result.changeList;
+                                node.is_change = 0;
+                                node.is_valuation = 0;
+                                node.camount = null;
+                                const billsNode = node;
+                                billsTreeSpreadObj.reCalcCamount(billsNode);
+                                const loadResult = { update: [billsNode] };
+                                const refreshNode = billsTree.loadPostData(loadResult);
+                                billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
+                                billsTreeSpreadObj.loadExprToInput(billsSheet);
+                            });
+                        } else {
+                            postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', {
+                                type: 'update',
+                                updateData: cInfo
+                            }, function (result) {
+                                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                                const billsNode = node;
+                                billsTreeSpreadObj.reCalcCamount(billsNode);
+                                const loadResult = {update: [billsNode]};
+                                const refreshNode = billsTree.loadPostData(loadResult);
+                                billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
+                            }, function () {
+                                cInfo[col.field] = orgValue;
+                                cInfo.spamount = orgValue;
+                                node[col.field] = orgValue;
+                                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            });
+                        }
                     } else {
                         // 更新至服务器
                         postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', {type: 'add-change-list', postData: [cInfo]}, function (result) {
@@ -1524,9 +1551,24 @@ $(document).ready(() => {
                 }
             }
             if (camountDatas.length > 0) {
-                postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', { type:'paste', updateData: camountDatas }, function (result) {
-                    changeList = result;
+                for (const cInfo of camountDatas) {
+                    if (changeOrder && _.findIndex(oldChangeList, {id: cInfo.id}) !== -1) {
+                        toastr.warning('插入台账清单功能下无法从这移除已勾选清单');
+                        return
+                    }
+                    if (_.find(changeUsedData, {cbid: cInfo.id})) {
+                        toastr.warning('清单计量单元已被使用,无法取消勾选');
+                        return
+                    } else if (checkIsSettle(cInfo)) {
+                        toastr.warning('清单计量单元已结算,无法取消勾选');
+                        info.sheet.setValue(info.row, info.col, 1);
+                        return
+                    }
+                }
+                postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', {type: 'del-change-list', ids: _.map(camountDatas, 'id') }, function (result) {
+                    changeList = result.changeList;
                     for (const d of datas) {
+                        d.is_change = 0;
                         billsTreeSpreadObj.reCalcCamount(d);
                     }
                     const loadResult = { update: datas };
@@ -1534,6 +1576,16 @@ $(document).ready(() => {
                     billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
                     billsTreeSpreadObj.loadExprToInput(billsSheet);
                 });
+                // postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', { type:'paste', updateData: camountDatas }, function (result) {
+                //     changeList = result;
+                //     for (const d of datas) {
+                //         billsTreeSpreadObj.reCalcCamount(d);
+                //     }
+                //     const loadResult = { update: datas };
+                //     const refreshNode = billsTree.loadPostData(loadResult);
+                //     billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
+                //     billsTreeSpreadObj.loadExprToInput(billsSheet);
+                // });
                 return;
             }
             if (datas.length > 0) {
@@ -2795,18 +2847,48 @@ $(document).ready(() => {
                 cInfo.camount_expr = exprQuantity.expr;
                 delete cInfo.waitingLoading;
                 if (cInfo.id) {
-                    postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', { type: 'update', updateData: cInfo }, function (result) {
-                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        const billsNode = node;
-                        billsTreeSpreadObj.reCalcCamount(billsNode);
-                        const loadResult = {update: [billsNode]};
-                        const refreshNode = billsTree.loadPostData(loadResult);
-                        billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
-                    }, function () {
-                        cInfo[col.field] = orgValue;
-                        cInfo.spamount = orgValue;
-                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                    });
+                    if (cInfo.camount === 0 && cInfo.camount_expr === '') {
+                        if (changeOrder && _.findIndex(oldChangeList, {id: cInfo.id}) !== -1) {
+                            toastr.warning('插入台账清单功能下无法从这移除已勾选清单');
+                            return
+                        }
+                        if (_.find(changeUsedData, {cbid: cInfo.id})) {
+                            toastr.warning('清单计量单元已被使用,无法取消勾选');
+                            return
+                        } else if (checkIsSettle(cInfo)) {
+                            toastr.warning('清单计量单元已结算,无法取消勾选');
+                            info.sheet.setValue(info.row, info.col, 1);
+                            return
+                        }
+                        postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', {type: 'del-change-list', ids: [cInfo.id] }, function (result) {
+                            changeList = result.changeList;
+                            node.is_change = 0;
+                            node.is_valuation = 0;
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            const billsNode = SpreadJsObj.getSelectObject(billsSheet);
+                            billsTreeSpreadObj.reCalcCamount(billsNode);
+                            // 判断是否只剩一个,并同步去勾
+                            if (_.findIndex(changeList, { gcl_id: node.lid }) === -1) {
+                                billsNode.is_change = 0;
+                            }
+                            const loadResult = { update: [billsNode] };
+                            const refreshNode = billsTree.loadPostData(loadResult);
+                            billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
+                        });
+                    } else {
+                        postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', { type: 'update', updateData: cInfo }, function (result) {
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            const billsNode = node;
+                            billsTreeSpreadObj.reCalcCamount(billsNode);
+                            const loadResult = {update: [billsNode]};
+                            const refreshNode = billsTree.loadPostData(loadResult);
+                            billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
+                        }, function () {
+                            cInfo[col.field] = orgValue;
+                            cInfo.spamount = orgValue;
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        });
+                    }
                 } else {
                     postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', {type: 'add-change-list', postData: [cInfo]}, function (result) {
                         changeList = result.changeList;
@@ -3013,10 +3095,35 @@ $(document).ready(() => {
                 }
             }
             if (camountDatas.length > 0) {
-                postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', { type:'paste', updateData: camountDatas }, function (result) {
-                    changeList = result;
+                for (const cInfo of camountDatas) {
+                    if (changeOrder && _.findIndex(oldChangeList, {id: cInfo.id}) !== -1) {
+                        toastr.warning('插入台账清单功能下无法从这移除已勾选清单');
+                        info.sheet.setValue(info.row, info.col, 1);
+                        return
+                    }
+                    if (_.find(changeUsedData, {cbid: cInfo.id})) {
+                        toastr.warning('该计量单元已被使用,无法取消变更');
+                        info.sheet.setValue(info.row, info.col, 1);
+                        return
+                    } else if (checkIsSettle(cInfo)) {
+                        toastr.warning('该计量单元已结算,无法取消变更');
+                        info.sheet.setValue(info.row, info.col, 1);
+                        return
+                    }
+                }
+
+                postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', {type: 'del-change-list', ids: _.map(camountDatas, 'id') }, function (result) {
+                    changeList = result.changeList;
+                    for (const d of datas) {
+                        d.is_change = 0;
+                        d.is_valuation = 0;
+                    }
                     const billsNode = SpreadJsObj.getSelectObject(billsSheet);
                     billsTreeSpreadObj.reCalcCamount(billsNode);
+                    // 判断是否只剩一个,并同步去勾
+                    if (_.findIndex(changeList, { gcl_id: posSelects[0].lid }) === -1) {
+                        billsNode.is_change = 0;
+                    }
                     const loadResult = { update: [billsNode] };
                     const refreshNode = billsTree.loadPostData(loadResult);
                     billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
@@ -3024,6 +3131,17 @@ $(document).ready(() => {
                 }, function () {
                     posSpreadObj.loadCurPosData();
                 });
+                // postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', { type:'paste', updateData: camountDatas }, function (result) {
+                //     changeList = result;
+                //     const billsNode = SpreadJsObj.getSelectObject(billsSheet);
+                //     billsTreeSpreadObj.reCalcCamount(billsNode);
+                //     const loadResult = { update: [billsNode] };
+                //     const refreshNode = billsTree.loadPostData(loadResult);
+                //     billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
+                //     posSpreadObj.loadCurPosData();
+                // }, function () {
+                //     posSpreadObj.loadCurPosData();
+                // });
                 return;
             }
             if (datas.length > 0) {

+ 4 - 3
app/public/js/div_resizer.js

@@ -100,9 +100,10 @@
             if (drag) {
                 drag = false;
                 const rType = obj.attr('r-type');
-                const localId = obj.attr('store-id'), div1 = $(obj.attr('div1')), div2 = $(obj.attr('div2'));
-                setLocalCache('v-resize-1-' + localId, div1[rType]());
-                setLocalCache('v-resize-2-' + localId, div2[rType]());
+                const localId = obj.attr('store-id'), version = obj.attr('store-version') ? ('-'+obj.attr('store-version')) : '' ;
+                const div1 = $(obj.attr('div1')), div2 = $(obj.attr('div2'));
+                setLocalCache('v-resize-1-' + localId + version, div1[rType]());
+                setLocalCache('v-resize-2-' + localId + version, div2[rType]());
             }
         });
     }

+ 44 - 27
app/public/js/file_detail.js

@@ -94,11 +94,11 @@ $(document).ready(function() {
             this.refreshFileCountHint();
         }
         _getFileNameHtml(file) {
-            const moveHtml = file.canEdit ? `<a href="javascript: void(0);" class="mr-1" name="move-file" fid="${file.id}"><i class="fa fa-exchange fa-fw"></i></a>` : '';
-            const editHtml = file.canEdit ? `<a href="javascript: void(0);" class="mr-1" name="edit-file" fid="${file.id}"><i class="fa fa-pencil fa-fw"></i></a>` : '';
+            const moveHtml = file.canEdit || canEdit ? `<a href="javascript: void(0);" class="mr-1" name="move-file" fid="${file.id}"><i class="fa fa-exchange fa-fw"></i></a>` : '';
+            const editHtml = file.canEdit || canEdit ? `<a href="javascript: void(0);" class="mr-1" name="edit-file" fid="${file.id}"><i class="fa fa-pencil fa-fw"></i></a>` : '';
             const viewHtml = file.viewpath ? `<a href="${file.viewpath}" class="mr-1" target="_blank"><i class="fa fa-eye fa-fw"></i></a>` : '';
             const downHtml = `<a href="javascript: void(0);" onclick="AliOss.downloadFile('${file.filepath}', '${file.filename + file.fileext}')" class="mr-1"><i class="fa fa-download fa-fw"></i></a>`;
-            const delHtml = file.canEdit || canDelete ? `<a href="javascript: void(0);" class="mr-1 text-danger" name="del-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i></a>` : '';
+            const delHtml = file.canEdit || canEdit ? `<a href="javascript: void(0);" class="mr-1 text-danger" name="del-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i></a>` : '';
             return `<div class="d-flex justify-content-between align-items-center table-file"><div name="filename" fid="${file.id}">${file.filename}${file.fileext}</div><div class="btn-group-table" style="display: none;">${moveHtml}${editHtml}${viewHtml}${downHtml}${delHtml}</div></div>`;
         }
         _getEditFileNameHtml(file) {
@@ -295,24 +295,31 @@ $(document).ready(function() {
             });
 
         }
-        delFiles(files, callback) {
-            postData('file/del', { del: files }, async function(data) {
-                const relaFiling = data.filing.id === filingObj.curFiling.source_node.id
-                    ? filingObj.curFiling : filingObj.findFiling(data.filing.id);
-                for (const id of data.del) {
-                    const fIndex = relaFiling.source_node.files.findIndex(x => { return x.id === id });
-                    if (fIndex >= 0) relaFiling.source_node.files.splice(fIndex, 1);
-                    fileSearch.removeSearchFile(id);
-                }
-                filingObj.updateFilingFileCount(relaFiling, data.filing.file_count);
-                await filingObj.loadFiles(relaFiling, filingObj.curPage);
-                if (data.filing.id === filingObj.curFiling.source_node.id) {
-                    filingObj.refreshPages();
-                    filingObj.refreshFilesTable();
-                    filingObj.refreshFileCountHint();
-                }
-                if (callback) callback();
-            });
+        delFiles(files, callback, hint = '') {
+            const del = function() {
+                postData('file/del', { del: files }, async function(data) {
+                    const relaFiling = data.filing.id === filingObj.curFiling.source_node.id
+                        ? filingObj.curFiling : filingObj.findFiling(data.filing.id);
+                    for (const id of data.del) {
+                        const fIndex = relaFiling.source_node.files.findIndex(x => { return x.id === id });
+                        if (fIndex >= 0) relaFiling.source_node.files.splice(fIndex, 1);
+                        fileSearch.removeSearchFile(id);
+                    }
+                    filingObj.updateFilingFileCount(relaFiling, data.filing.file_count);
+                    await filingObj.loadFiles(relaFiling, filingObj.curPage);
+                    if (data.filing.id === filingObj.curFiling.source_node.id) {
+                        filingObj.refreshPages();
+                        filingObj.refreshFilesTable();
+                        filingObj.refreshFileCountHint();
+                    }
+                    if (callback) callback();
+                });
+            };
+            if (hint) {
+                deleteAfterHint(del, hint);
+            } else {
+                del();
+            }
         }
         renameFile(file, filename) {
             const self = this;
@@ -724,7 +731,8 @@ $(document).ready(function() {
     });
     $('body').on('click', "a[name=del-file]", function() {
         const del = [this.getAttribute('fid')];
-        filingObj.delFiles(del);
+        const filename = $('[name=filename]', $(this).closest('td'))[0].innerHTML;
+        filingObj.delFiles(del, null, `确认删除「${filename}」?`);
     });
     $('body').on('click', "a[name=edit-file]", function() {
         const check = $('[name=filename] input[fid]');
@@ -911,7 +919,7 @@ $(document).ready(function() {
                 const file = filingObj.curFiling.source_node.files.find(x => { return x.id === fid });
                 if (!file) continue;
 
-                if (file.user_id !== userID) {
+                if (file.user_id !== userID && !canEdit) {
                     toastr.error(`文件【${file.filename + file.fileext}】不是您上传的文件,请勿删除`);
                     return;
                 }
@@ -1655,16 +1663,16 @@ $(document).ready(function() {
         }
         getOperateHtml(file) {
             const locateHtml = `<a href="javascript: void(0);" class="mr-1" name="locate-search-file" fid="${file.id}"><i class="fa fa-crosshairs"></i></a>`;
-            const editHtml = file.canEdit ? `<a href="javascript: void(0);" class="mr-1" name="edit-search-file" fid="${file.id}"><i class="fa fa-pencil fa-fw"></i></a>` : '';
+            const editHtml = file.canEdit || canEdit ? `<a href="javascript: void(0);" class="mr-1" name="edit-search-file" fid="${file.id}"><i class="fa fa-pencil fa-fw"></i></a>` : '';
             const viewHtml = file.viewpath ? `<a href="${file.viewpath}" class="mr-1" target="_blank"><i class="fa fa-eye fa-fw"></i></a>` : '';
             const downHtml = `<a href="javascript: void(0);" onclick="AliOss.downloadFile('${file.filepath}', '${file.filename + file.fileext}')" class="mr-1"><i class="fa fa-download fa-fw"></i></a>`;
-            const delHtml = file.canEdit ? `<a href="javascript: void(0);" class="mr-1 text-danger" name="del-search-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i></a>` : '';
+            const delHtml = file.canEdit || canEdit ? `<a href="javascript: void(0);" class="mr-1 text-danger" name="del-search-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i></a>` : '';
             return `<div class="d-flex justify-content-between align-items-center table-file">${locateHtml}${editHtml}${viewHtml}${downHtml}${delHtml}</div>`
         }
         getFileHtml(file) {
             const html = [];
             html.push(`<tr fid="search_${file.id}">`);
-            html.push(`<td fid="search_${file.id}">${file.filename}${file.fileext}</td>`);
+            html.push(`<td name="filename" fid="search_${file.id}">${file.filename}${file.fileext}</td>`);
             html.push(`<td class="text-center">${file.user_name}</td>`);
             html.push(`<td class="text-center">${this.getOperateHtml(file)}</td>`);
             html.push('</tr>');
@@ -1738,7 +1746,8 @@ $(document).ready(function() {
             $('button', '#search').bind('click', () => { self.search(); });
             $('body').on('click', "a[name=del-search-file]", function() {
                 const del = [this.getAttribute('fid')];
-                filingObj.delFiles(del);
+                const filename = $('[name=filename]', $(this).closest('tr'))[0].innerHTML;
+                filingObj.delFiles(del, null, `确认删除「${filename}」?`);
             });
             $('body').on('click', "a[name=locate-search-file]", function() {
                 const fid = this.getAttribute('fid');
@@ -1876,4 +1885,12 @@ $(document).ready(function() {
             }, 100);
         });
     })('a[name=showLevel]');
+
+
+    $.divResizer({
+        select: '#file-right-spr',
+        callback: function() {
+            if (fileReference) fileReference.spread.refresh();
+        },
+    });
 });

+ 22 - 634
app/public/js/financial_pay.js

@@ -1,3 +1,4 @@
+'use strict';
 let auditUtils;
 $(function () {
     autoFlashHeight();
@@ -26,6 +27,9 @@ $(function () {
 
     function setSelectValue(select, value) {
         const routes = [];
+        if (from) {
+            routes.push('from=' + from);
+        }
         const tid = select === 'tid' ? value : $('#tid_select').val();
         if (tid) {
             routes.push('tid=' + tid);
@@ -38,541 +42,11 @@ $(function () {
         if (used) {
             routes.push('used=' + used);
         }
-        window.location.href = `/sp/${spid}/financial/pay` + (routes.length ? '?' + routes.join('&') : '');
-    }
-
-    let timer = null
-    let oldSearchVal = null
-    $('#liucheng').on('input propertychange', '.gr-search', function(e) {
-        oldSearchVal = e.target.value;
-        timer && clearTimeout(timer);
-        timer = setTimeout(() => {
-            const newVal = $(this).val();
-            const code = $(this).attr('data-code');
-            let html = '';
-            if (newVal && newVal === oldSearchVal) {
-                accountList.filter(item => item && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
-                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
-                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
-                                class="ml-auto">${item.mobile || ''}</span></p>
-                        <span class="text-muted">${item.role || ''}</span>
-                    </dd>`
-                });
-                $('#' + code + '_dropdownMenu .book-list').empty();
-                $('#' + code + '_dropdownMenu .book-list').append(html);
-            } else {
-                if (!$('#' + code + '_dropdownMenu .acc-btn').length) {
-                    accountGroup.forEach((group, idx) => {
-                        if (!group) return;
-                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
-                        </a> ${group.groupName}</dt>
-                        <div class="dd-content" data-toggleid="${idx}">`;
-                        group.groupList.forEach(item => {
-                            html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
-                                <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
-                                        class="ml-auto">${item.mobile || ''}</span></p>
-                                <span class="text-muted">${item.role || ''}</span>
-                            </dd>`;
-                        });
-                        html += '</div>';
-                    });
-                    $('#' + code + '_dropdownMenu .book-list').empty();
-                    $('#' + code + '_dropdownMenu .book-list').append(html);
-                }
-            }
-        }, 400);
-    });
-
-    $('#liucheng').on('show.bs.modal', function (e) {
-        $('#shenpi-tender-list tr').removeClass('bg-warning');
-        if (tenders.length > 0) {
-            $('#shenpi-tender-list tr').eq(0).addClass('bg-warning');
-            auditUtils. makeReportListHtml(tenders[0]);
-            auditUtils.makeShenpiListHtml(tenders[0]);
-        }
-    });
-
-    $('#shenpi-tender-list').on('click', '.change-tender', function () {
-        if ($(this).hasClass('bg-warning')) {
-            return;
-        }
-        $('#shenpi-tender-list tr').removeClass('bg-warning');
-        $(this).parents('tr').addClass('bg-warning');
-        const tid = parseInt($(this).parents('tr').data('tid')) || 0;
-        const tender = tenders.find(t => t.id === tid);
-        if (!tender) {
-            toastr.error('请选择标段');
-            return;
-        }
-        auditUtils.makeReportListHtml(tender);
-        auditUtils.makeShenpiListHtml(tender);
-    });
-    auditUtils = {
-        makeReportListHtml: function (flow) {
-            let addHtml = '';
-            $('#select-all-ptAudits').prop('checked', false);
-            for (const pl of flow.permissionList) {
-                addHtml += `<tr>
-                                <td class="text-center"><input type="checkbox" class="select-ptAudit" data-id="${pl.id}"></td><td>${pl.name}</td><td>${pl.company}</td>
-                                <td class="text-center"><input type="checkbox" class="save-report" data-id="${pl.id}" ${pl.is_report ? 'checked' : ''}></td><td class="text-center"><a href="javascript:void(0);" class="text-danger remove-audit" data-id="${pl.id}">移除</a></td>
-                            </tr>`;
-            }
-            $('#report-list').html(addHtml);
-        },
-        makeShenpiListHtml: function (flow) {
-            let addhtml = '<ul class="list-unstyled">\n';
-            addhtml += this.getgdsplHtml(flow, 'financial');
-            addhtml += '</ul>\n';
-            $('#shenpi-list').html(addhtml);
-        },
-        getAuditHtml: function(audit, is_report = 0) {
-            return '<span class="d-inline-block"><span class="badge badge-light">'+ audit.name +' <span class="dropdown">\n' +
-                '                                                            <a href="javascript:void(0);" class="btn-sm text-danger px-1" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-remove"></i></a>\n' +
-                '                                                            <div class="dropdown-menu">\n' +
-                '                                                                <a class="dropdown-item" href="javascript:void(0);">确认移除' + ( is_report ? '填报人' : '审批人') + '?</a>\n' +
-                '                                                                <div class="dropdown-divider"></div>\n' +
-                '                                                                <div class="px-2 py-1 text-center">\n' +
-                '                                                                    <button class="remove-audit btn btn-sm btn-danger" data-id="' + (is_report ? audit.id : audit.audit_id) + '">移除</button>\n' +
-                '                                                                    <button class="btn btn-sm btn-secondary">取消</button>\n' +
-                '                                                                </div>\n' +
-                '                                                            </div>\n' +
-                '                                                        </span> ' +
-                '                                            </span></span>\n'
-        },
-        getAuditTypeHtml: function(code, type) {
-            const html = [];
-            html.push(`<span class="d-inline-block"><select class="form-control form-control-sm audit-type-key" data-type="${type}">`);
-            for (const t of auditType.types) {
-                if (t.valid && t.valid.indexOf(code) < 0) continue;
-                html.push(`<option value="${t.value}" ${t.value === type ? 'selected' : ''}>${t.name}</option>`);
-            }
-            html.push('</select></span> ');
-            return html.join('');
-        },
-        getSelectAuditHtml: function (code, is_report = 0) {
-            let divhtml = '';
-            accountGroup.forEach((group, idx) => {
-                let didivhtml = '';
-                if(group) {
-                    group.groupList.forEach(item => {
-                        didivhtml += '<dd class="border-bottom p-2 mb-0 " data-id="' + item.id + '" >\n' +
-                            '<p class="mb-0 d-flex"><span class="text-primary">' + item.name + '</span><span\n' +
-                            '                                                                                class="ml-auto">' + item.mobile + '</span></p>\n' +
-                            '                                                                    <span class="text-muted">' + item.role + '</span>\n' +
-                            '                                                                    </dd>\n';
-                    });
-                    divhtml += '<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="' + idx + '" data-type="hide"><i class="fa fa-plus-square"></i></a> ' + group.groupName + '</dt>\n' +
-                        '                                                                <div class="dd-content" data-toggleid="' + idx + '">\n' + didivhtml +
-                        '                                                                </div>\n';
-                }
-            });
-            const html =
-                '                                            <span class="d-inline-block">\n' +
-                '                                                <div class="dropdown text-right">\n' +
-                '                                                    <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="' + code + '_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n' +
-                '                                                        ' + (is_report ? '添加填报人' : '选择审批人') + '\n' +
-                '                                                    </button>\n' +
-                '                                                    <div class="dropdown-menu dropdown-menu-right" id="' + code + '_dropdownMenu" aria-labelledby="' + code + '_dropdownMenuButton" style="width:220px">\n' +
-                '                                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"\n' +
-                '                                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="' + code + '"></div>\n' +
-                '                                                        <dl class="list-unstyled book-list">\n' + divhtml +
-                '                                                        </dl>\n' +
-                '                                                    </div>\n' +
-                '                                                </div>\n' +
-                '                                            </span>\n';
-            return html;
-        },
-        // 以下i从1开始
-        getAuditGroupInnerHtml: function(code, auditGroup, i) {
-            const html = [];
-            const type = auditGroup.length > 0 ? auditGroup[0].audit_type : auditType.key.common;
-            html.push(`<span class="col-auto">${transFormToChinese(i)}审</span><span class="col-10 spr-span">`);
-            html.push(this.getAuditTypeHtml(code, type));
-            for (const audit of auditGroup) {
-                html.push(this.getAuditHtml(audit));
-            }
-            if (type !== auditType.key.common || auditGroup.length === 0) {
-                html.push(this.getSelectAuditHtml(code));
-            }
-            if (type === auditType.key.union && auditGroup.length > 0) {
-                html.push(`<button class="btn btn-sm btn-outline-primary" sp_type="${code}" audit_order="${i}" name="union-set">协同设置</button>`);
-            }
-            html.push('</span>');
-            return html.join('');
-        },
-        getAuditGroupHtml: function (code, auditGroup, i) {
-            return `<li class="d-flex justify-content-start align-items-center mb-3">${this.getAuditGroupInnerHtml(code, auditGroup, i)}</li>`;
-        },
-        getgdsplHtml(flow, this_code) {
-            let addhtml = '';
-            if (flow.auditGroupList.length !== 0) {
-                for(const [i, auditGroup] of flow.auditGroupList.entries()) {
-                    addhtml += this.getAuditGroupHtml(this_code, auditGroup, i + 1);
-                }
-                const addGroupHtml = this_code === 'change' && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
-                    `<span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="${this_code}"><i class="fa fa-save"></i> 存为审批组</a></span>\n` : '';
-                addhtml += '<li>\n' +
-                    '                                            <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>\n' + addGroupHtml +
-                    '                                        </li>';
-            } else {
-                addhtml += this.getAuditGroupHtml(this_code, [], 1);
-            }
-            return addhtml;
-        },
-        // 以下i从0开始
-        addAudit: function (tender, user, i) {
-            const flow = tender;
-            if (!flow.auditGroupList) flow.auditGroupList = [];
-            if (!flow.auditGroupList[i]) flow.auditGroupList[i] = [];
-            flow.auditGroupList[i].push(user);
-            return flow.auditGroupList[i];
-        },
-        removeAudit: function (tender, audit_id, i) {
-            const flow = tender;
-            if (flow.auditGroupList[i].length === 1) {
-                flow.auditGroupList.splice(i, 1);
-                return null;
-            }
-            flow.auditGroupList[i].splice(flow.auditGroupList[i].findIndex(x => { return x.audit_id === audit_id; }), 1);
-            return flow.auditGroupList[i];
-        },
-        setAuditType: function (tender, audit_type, i) {
-            const flow = tender;
-            if (!flow || !flow.auditGroupList || !flow.auditGroupList[i]) return;
-            flow.auditGroupList[i].forEach(x => { x.audit_type = audit_type});
-            return flow.auditGroupList[i];
-        }
-    };
-
-    // 选中填报人
-    $('body').on('click', 'div[id="report_audit_dropdownMenu"] dl dd', function () {
-        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
-        const tender = tenders.find(t => t.id === tid);
-        if (!tender) {
-            toastr.error('请选择标段');
-            return;
-        }
-        const id = parseInt($(this).data('id'));
-        if (!id) return;
-        if (!isNaN(id) && id !== 0) {
-            postData(`/sp/${spid}/financial/pay/save`, {type: 'add-tender-audit', id: id, tid: tender.id }, function (result) {
-                tender.permissionList = result;
-                auditUtils.makeReportListHtml(tender);
-            })
-        }
-    });
-
-    // 移除填报人
-    $('body').on('click', '#report-list .remove-audit', function () {
-        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
-        const tender = tenders.find(t => t.id === tid);
-        if (!tender) {
-            toastr.error('请选择标段');
-            return;
-        }
-        const id = parseInt($(this).data('id'));
-        deleteAfterHint(function () {
-            postData(`/sp/${spid}/financial/pay/save`, {type: 'del-tender-audit', id, tid: tender.id }, function (result) {
-                tender.permissionList = result.permissionList;
-                tender.auditGroupList = result.auditGroupList;
-                auditUtils.makeReportListHtml(tender);
-                auditUtils.makeShenpiListHtml(tender);
-            });
-        }, '确认删除该标段用户?');
-    });
-
-    // 勾选是否为填报人
-    $('body').on('click', '#report-list .save-report', function () {
-        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
-        const tender = tenders.find(t => t.id === tid);
-        if (!tender) {
-            toastr.error('请选择标段');
-            return;
-        }
-        const id = parseInt($(this).data('id'));
-        const isReport = $(this).prop('checked');
-        const permission = tender.permissionList.find(p => p.id === id);
-        if (!permission) {
-            toastr.error('该用户不存在');
-            return;
-        }
-        postData(`/sp/${spid}/financial/pay/save`, {type: 'save-permission', updateData: { id, is_report: isReport } }, function (result) {
-            permission.is_report = isReport;
-        });
-    });
-
-    $('#select-all-ptAudits').click(function () {
-        $('#report-list .select-ptAudit').prop('checked', $(this).prop('checked'));
-    });
-
-    $('#batch-del-ptAudit').click(function () {
-        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
-        const tender = tenders.find(t => t.id === tid);
-        if (!tender) {
-            toastr.error('请选择标段');
-            return;
-        }
-        const ids = [];
-        $('#report-list .select-ptAudit:checked').each(function () {
-            ids.push(parseInt($(this).data('id')));
-        });
-        if (ids.length === 0) {
-            toastr.warning('请勾选要删除的用户');
-            return;
+        if (getLocalCache('account-pageSize')) {
+            routes.push('pageSize=' + getLocalCache('account-pageSize'));
         }
-        deleteAfterHint(function () {
-            postData(`/sp/${spid}/financial/pay/save`, {type: 'del-tender-audit', id: ids, tid: tender.id }, function (result) {
-                tender.permissionList = result.permissionList;
-                tender.auditGroupList = result.auditGroupList;
-                auditUtils.makeReportListHtml(tender);
-                auditUtils.makeShenpiListHtml(tender);
-            });
-        }, '确认删除已勾选的标段用户?');
-    });
-
-    $('#batch-other-ptAudit').click(function () {
-        const cur_tenderid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
-        const tender = tenders.find(t => t.id === cur_tenderid);
-        if (!tender) {
-            toastr.error('请选择标段');
-            return;
-        }
-        const ids = [];
-        $('#report-list .select-ptAudit:checked').each(function () {
-            ids.push(parseInt($(this).data('id')));
-        });
-        if (ids.length === 0) {
-            toastr.warning('请勾选要同步的用户');
-            return;
-        }
-        const num = $('#shenpi-tender-list input:checked').length;
-        if (num === 0 || (num === 1 && parseInt($('#shenpi-tender-list input:checked').eq(0).parents('tr').data('tid')) === cur_tenderid)) {
-            toastr.warning('请选择需要设置审批同步的标段');
-            return;
-        }
-        const tenderList = [];
-        for (let i = 0; i < num; i++) {
-            const tid = parseInt($('#shenpi-tender-list input:checked').eq(i).parents('tr').data('tid'));
-            if (tid !== cur_tenderid) {
-                tenderList.push(tid);
-            }
-        }
-        const data = {
-            type: 'copy-tender-audit',
-            id: ids,
-            this_tid: tender.id,
-        }
-        data.tidList = tenderList.join(',');
-        postData(`/sp/${spid}/financial/pay/save`,  data, function (result) {
-            toastr.success('已同步到其它勾选标段');
-            for (let i = 0; i < num; i++) {
-                const tid = parseInt($('#shenpi-tender-list input:checked').eq(i).parents('tr').data('tid'));
-                if (tid !== cur_tenderid) {
-                    const other_tender = tenders.find(t => t.id === tid);
-                    const permissionList = _.filter(result.otherPermissionList, { tid: tid });
-                    other_tender.permissionList = permissionList;
-                }
-            }
-        });
-    });
-
-    // 选中审批人
-    $('body').on('click', '#shenpi-list div[id$="_dropdownMenu"] dl dd', function () {
-        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
-        const tender = tenders.find(t => t.id === tid);
-        if (!tender) {
-            toastr.error('请选择标段');
-            return;
-        }
-        const id = parseInt($(this).data('id'));
-        if (!id) return;
-
-        let this_code = 'financial';
-        const user = _.find(accountList, function (item) {
-            return item.id === id;
-        });
-        // 判断是否已存在审批人
-        const aid_num = $(this).parents('ul').find('.remove-audit').length;
-        for (let i = 0; i < aid_num; i++) {
-            const aid = parseInt($(this).parents('ul').find('.remove-audit').eq(i).data('id'));
-            if (aid === id) {
-                toastr.warning('该审核人已存在,请勿重复添加');
-                return;
-            }
-        }
-        const prop = {
-            status: sp_status.gdspl,
-            code: sp_type[this_code],
-            audit_id: id,
-            type: 'add',
-            audit_type: parseInt($(this).parents('li').find('select[class*="audit-type-key"]')[0].value),
-            audit_order: $(this).parents('li').index() + 1,
-        };
-        const _self = $(this);
-        postData(`/sp/${spid}/financial/pay/save`, { type: 'add-shenpi-audit', shenpi: prop, tid }, function (result) {
-            const data = result.shenpi;
-            const auditGroup = auditUtils.addAudit(tender, { audit_id: data.audit_id, name: user.name, audit_type: data.audit_type, audit_order: data.audit_order }, prop.audit_order - 1);
-            if (_self.parents('ul').find('.add-audit').length === 0) {
-                _self.parents('ul').append('<li>\n' +
-                    '                                            <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>\n' +
-                    '                                        </li>');
-            }
-            _self.parents('li').html(auditUtils.getAuditGroupInnerHtml(this_code, auditGroup, prop.audit_order));
-            tender.permissionList = result.permissionList;
-            auditUtils.makeReportListHtml(tender);
-        });
-    });
-
-    // 移除审批人
-    $('body').on('click', '#shenpi-list .remove-audit', function () {
-        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
-        const tender = tenders.find(t => t.id === tid);
-        if (!tender) {
-            toastr.error('请选择标段');
-            return;
-        }
-        const id = parseInt($(this).data('id'));
-        const this_code = 'financial';
-        const prop = {
-            status: sp_status.gdspl,
-            code: sp_type[this_code],
-            audit_id: id,
-            type: 'del',
-        };
-        const _self = $(this);
-        postData('/tender/' + tid + '/shenpi/audit/save', prop, function (data) {
-            const index = _self.parents('li').index();
-            const auditGroup = auditUtils.removeAudit(tender, id, index);
-            if (auditGroup) {
-                _self.parents('li').html(auditUtils.getAuditGroupInnerHtml(this_code, auditGroup, index + 1));
-            } else {
-                const _selflc = _self.parents('#shenpi-list');
-                _self.parents('li').remove();
-                const aid_num = parseInt(_selflc.children('ul').find('li.d-flex').length);
-                if (aid_num === 0) {
-                    _selflc.children('ul').html(auditUtils.getAuditGroupHtml(this_code, [], 1));
-                } else {
-                    for (let i = 0; i < aid_num; i++) {
-                        _selflc.find('li.d-flex').eq(i).find('.col-auto').text(transFormToChinese(i+1) + '审');
-                    }
-                }
-            }
-        })
-    });
-
-    $('body').on('click', '#shenpi-list .add-audit', function () {
-        const num = $(this).parents('ul').children('li').length;
-        const this_code = 'financial';
-        const addhtml = auditUtils.getAuditGroupHtml(this_code, [], num);
-        $(this).parents('ul').append(addhtml);
-        $(this).parents('li').remove();
-    });
-
-    // 设置会签、或签
-    $('body').on('change', 'select[class*="audit-type-key"]', function() {
-        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
-        const tender = tenders.find(t => t.id === tid);
-        if (!tender) {
-            toastr.error('请选择标段');
-            return;
-        }
-        const removes = $(this).parents('.d-flex').find('.remove-audit');
-        if (removes.length === 0) return;
-
-        const this_status = sp_status.gdspl;
-        const this_code = 'financial';
-        const ids = [];
-        const liParent = $(this).parents('li');
-        removes.each((i, r) => { ids.push(parseInt(r.getAttribute('data-id'))); });
-        const prop = {
-            status: this_status,
-            code: sp_type[this_code],
-            audit_id: ids,
-            audit_type: parseInt(this.value),
-            type: 'audit-type',
-        };
-        if (prop.audit_type === auditType.key.common && ids.length > 1) {
-            toastr.warning('设置个人审批前请先删除多余的审批人');
-            this.value = this.getAttribute('data-type');
-            return;
-        }
-        const _self = this;
-        postData('/tender/'+ tid +'/shenpi/audit/save', prop, function () {
-            _self.setAttribute('data-type', _self.value);
-            const auditGroup = auditUtils.setAuditType(tender, prop.audit_type, liParent.index());
-            liParent.html(auditUtils.getAuditGroupInnerHtml(this_code, auditGroup, liParent.index() + 1));
-        });
-    });
-
-    $('#set-other-tenders').on('click', function () {
-        const cur_tenderid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
-        const tender = tenders.find(t => t.id === cur_tenderid);
-        if (!tender) {
-            toastr.error('请选择标段');
-            return;
-        }
-        if (tender.auditGroupList.length === 0) {
-            toastr.warning('请先设置审批流程再同步到其它标段');
-            return;
-        }
-        const this_code = 'financial';
-        const num = $('#shenpi-tender-list input:checked').length;
-        if (num === 0 || (num === 1 && parseInt($('#shenpi-tender-list input:checked').eq(0).parents('tr').data('tid')) === cur_tenderid)) {
-            toastr.warning('请选择需要设置审批同步的标段');
-            return;
-        }
-        const data = {
-            type: 'copy-shenpi-audit',
-            status: sp_status.gdspl,
-            code: this_code,
-            this_tid: cur_tenderid,
-        };
-        // 二维数组变一维
-        data.auditList = [];
-        for (const auditGroup of tender.auditGroupList) {
-            data.auditList.push(...auditGroup);
-        }
-        const tenderList = [];
-        for (let i = 0; i < num; i++) {
-            const tid = parseInt($('#shenpi-tender-list input:checked').eq(i).parents('tr').data('tid'));
-            if (tid !== cur_tenderid) {
-                tenderList.push(tid);
-            }
-        }
-        data.tidList = tenderList.join(',');
-        console.log(data);
-        postData(`/sp/${spid}/financial/pay/save`, data, function (result) {
-            toastr.success('已同步到其它勾选标段');
-            for (let i = 0; i < num; i++) {
-                const tid = parseInt($('#shenpi-tender-list input:checked').eq(i).parents('tr').data('tid'));
-                if (tid !== cur_tenderid) {
-                    const other_tender = tenders.find(t => t.id === tid);
-                    other_tender.auditGroupList = tender.auditGroupList;
-                    const permissionList = _.filter(result.otherPermissionList, { tid: tid });
-                    other_tender.permissionList = permissionList;
-                }
-            }
-        });
-    });
-
-    // 添加到成员中
-    $('body').on('click', '.book-list dt', function () {
-        const idx = $(this).find('.acc-btn').attr('data-groupid')
-        const type = $(this).find('.acc-btn').attr('data-type')
-        if (type === 'hide') {
-            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
-                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
-                $(this).find('.acc-btn').attr('data-type', 'show')
-
-            })
-        } else {
-            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
-                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
-                $(this).find('.acc-btn').attr('data-type', 'hide')
-            })
-        }
-        return false
-    });
+        window.location.href = `/sp/${spid}/financial/pay/stage/${fpsid}` + (routes.length ? '?' + routes.join('&') : '');
+    }
 
     $('#add-pay').on('show.bs.modal', function () {
         let t = null;
@@ -612,7 +86,7 @@ $(function () {
             code: code,
             used: $('#add-pay-used').val(),
         };
-        postData(`/sp/${spid}/financial/pay/save`, { type: 'add-pay', updateData: prop }, function (result) {
+        postData(`/sp/${spid}/financial/pay/stage/${fpsid}/save`, { type: 'add-pay', updateData: prop }, function (result) {
             window.location.href = `/sp/${spid}/financial/pay/${result.id}/detail`;
         });
     });
@@ -620,114 +94,28 @@ $(function () {
     $('body').on('click', '#pay-list .del-pay-btn', function () {
         const fpid = $(this).data('id');
         deleteAfterHint(function () {
-            postData(`/sp/${spid}/financial/pay/save`, {type: 'del-pay', postData: { node: fpid }}, function (result) {
+            postData(`/sp/${spid}/financial/pay/stage/${fpsid}/save`, {type: 'del-pay', postData: { node: fpid }}, function (result) {
                 window.location.reload();
             })
         }, '确认删除该资金支付?');
     });
 
-    $('#pay-list tr').on('click', function () {
-        const tid = parseInt($(this).data('tid'));
-        const tender = tenders.find(t => t.id === tid);
-        if (tid && tender) {
-            $(this).siblings().removeClass('alert-warning');
-            $(this).addClass('alert-warning');
-            $('#show-pay-account').show();
-            $('#payaccount input[name="tid"]').val(tender.id);
-        } else {
-            $(this).siblings().removeClass('alert-warning');
-            $('#show-pay-account').hide();
-            $('#payaccount input[name="tid"]').val('');
-        }
-    });
-
-    $('#payaccount').on('show.bs.modal', function () {
-        const tid = parseInt($('#payaccount input[name="tid"]').val());
-        const tender = tenders.find(t => t.id === tid);
-        $('#payaccount .modal-title').text('付款账号()');
-        $('#payaccount input[name="name"]').val('');
-        $('#payaccount input[name="bank"]').val('');
-        $('#payaccount input[name="bank_account"]').val('');
-        $('#payaccount input[name="contact"]').val('');
-        $('#payaccount input[name="phone"]').val('');
-        if (tid && tender) {
-            $('#payaccount .modal-title').text('付款账号(' + tender.name + ')');
-            $('#payaccount input[name="name"]').val(tender.pt.name);
-            $('#payaccount input[name="bank"]').val(tender.pt.bank);
-            $('#payaccount input[name="bank_account"]').val(tender.pt.bank_account);
-            $('#payaccount input[name="contact"]').val(tender.pt.contact);
-            $('#payaccount input[name="phone"]').val(tender.pt.phone);
-            if (is_admin || (fptReportTids && _.includes(fptReportTids, tid))) {
-                $('#payaccount table input').attr('readonly', false);
-                $('#get-form-tender').show();
-                $('#set-pay-btn').show();
-                $('#payaccount input[name="id"]').val(tender.pt.id);
-            } else {
-                $('#payaccount table input').attr('readonly', true);
-                $('#get-form-tender').hide();
-                $('#set-pay-btn').hide();
-                $('#payaccount input[name="id"]').val('');
-                $('#payaccount input[name="tid"]').val('');
-            }
-        }
-    });
-
-    $('#get-form-tender').on('click', function () {
-        const tid = $('#payaccount input[name="tid"]').val();
-        const tender = tenders.find(t => t.id === parseInt(tid));
-        if (!tid || !tender) {
-            toastr.error('标段不存在');
-            return;
-        }
-        $('#payaccount input[name="name"]').val(tender.pay_account.name);
-        $('#payaccount input[name="bank"]').val(tender.pay_account.bank);
-        $('#payaccount input[name="bank_account"]').val(tender.pay_account.account);
-        $('#payaccount input[name="contact"]').val(tender.pay_account.contact);
-        $('#payaccount input[name="phone"]').val(tender.pay_account.phone);
-        toastr.success('已同步');
-    });
-
-    $('#set-pay-btn').on('click', function () {
-        const tid = $('#payaccount input[name="tid"]').val();
-        const tender = tenders.find(t => t.id === parseInt(tid));
-        if (!tid || !tender) {
-            toastr.error('标段不存在');
-            return;
-        }
-        if (is_admin || (fptReportTids && _.includes(fptReportTids, tender.id))) {
-            const data = {
-                id: parseInt($('#payaccount input[name="id"]').val()),
-                tid: tender.id,
-                name: $('#payaccount input[name="name"]').val(),
-                bank: $('#payaccount input[name="bank"]').val(),
-                bank_account: $('#payaccount input[name="bank_account"]').val(),
-                contact: $('#payaccount input[name="contact"]').val(),
-                phone: $('#payaccount input[name="phone"]').val(),
-            }
-            if (!data.name) {
-                toastr.error('请填写开户名称');
-                return;
-            }
-            if (!data.bank) {
-                toastr.error('请填写开户银行');
-                return;
-            }
-            if (!data.bank_account) {
-                toastr.error('请填写开户账号');
-                return;
-            }
-            postData(`/sp/${spid}/financial/pay/save`, { type: 'set-pay-tender', updateData: data }, function (result) {
-                toastr.success('保存成功');
-                tender.pt = result;
-            });
-        } else {
-            toastr.error('无权限操作');
+    $('#batch-old-pays').click(function () {
+        const pays = [];
+        $('#contract-old-pay input[type="checkbox"]:checked').each(function () {
+            const fpid = parseInt($(this).val());
+            pays.push(fpid);
+        });
+        if (pays.length === 0) {
+            toastr.error('请选择关联的旧数据');
             return;
         }
+        console.log(pays);
+        postData(`/sp/${spid}/financial/pay/stage/${fpsid}/save`, {type: 'batch-old-pays', postData: { payIds: pays }}, function (result) {
+            window.location.reload();
+        });
     });
 
-    $('#pay-list tr').eq(0).click();
-
     function changeTender(tender) {
         $('#add-pay-tender').val(tender ? tender.id : '');
         if (!tender) {

+ 262 - 0
app/public/js/financial_pay_list.js

@@ -0,0 +1,262 @@
+'use strict';
+let auditUtils;
+$(function () {
+    autoFlashHeight();
+
+    $('#tid_select').select2({
+        language: 'zh-CN',
+        theme: 'bootstrap4',
+        selectOnClose: true,
+        // width: '150',
+    });
+
+    $('#company_select').change(function () {
+        const company_id = parseInt($(this).val()) || 0;
+        setSelectValue('company', company_id);
+    });
+
+    $('#order_select').change(function () {
+        const qi = parseInt($(this).val()) || 0;
+        setSelectValue('qi', qi);
+    });
+
+    $('#tid_select').change(function () {
+        const tid = parseInt($(this).val()) || 0;
+        setSelectValue('tid', tid);
+    });
+
+    $('#status_select .to-log-link').click(function () {
+        const status = parseInt($(this).data('val')) || null;
+        setSelectValue('status', status);
+    });
+
+    $('#used_select .to-log-link').click(function () {
+        const used = $(this).data('val') || null;
+        setSelectValue('used', used);
+    });
+
+    function setSelectValue(select, value) {
+        const routes = [];
+        const company_id = select === 'company' ? value : parseInt($('#company_select').val());
+        if (company_id) {
+            const companyInfo = _.find(userCompanyList, { id: company_id });
+            if (companyInfo) routes.push('company=' + companyInfo.name);
+        }
+        const qi = select === 'qi' ? value : parseInt($('#order_select').val());
+        if (qi) {
+            routes.push('qi=' + qi);
+        }
+        const tid = select === 'tid' ? value : $('#tid_select').val();
+        if (tid) {
+            routes.push('tid=' + tid);
+        }
+        const status = select === 'status' ? value : $('#status_selected').data('value');
+        if (status) {
+            routes.push('status=' + status);
+        }
+        const used = select === 'used' ? value : $('#used_selected').data('value');
+        if (used) {
+            routes.push('used=' + used);
+        }
+        if (getLocalCache('account-pageSize')) {
+            routes.push('pageSize=' + getLocalCache('account-pageSize'));
+        }
+        window.location.href = `/sp/${spid}/financial/pay/list` + (routes.length ? '?' + routes.join('&') : '');
+    }
+
+    $('body').on('click', '#pay-list .del-pay-btn', function () {
+        const fpid = $(this).data('id');
+        deleteAfterHint(function () {
+            postData(`/sp/${spid}/financial/pay/save`, {type: 'del-pay', postData: { node: fpid }}, function (result) {
+                window.location.reload();
+            })
+        }, '确认删除该资金支付?');
+    });
+
+    $('#audit-list').on('click', 'a', function() {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+
+    // 获取审批流程
+    $('a[data-target="#sp-list" ]').on('click', function () {
+        const data = {
+            type: 'get-auditors',
+            id: $(this).attr('c-id'),
+        };
+        postData(`/sp/${spid}/financial/pay/save`, data, function (result) {
+            const { auditHistory, auditors2, user } = result;
+            let auditorsHTML = [];
+            auditors2.forEach((group, idx) => {
+                if (idx === 0) {
+                    auditorsHTML.push(`<li class="list-group-item d-flex justify-content-between align-items-center">
+                        <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
+                    <span class="text-muted">${getGroupAuditHtml(group)}</span>
+                    <span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>
+                    </li>`);
+                } else if(idx === auditors2.length -1 && idx !== 0) {
+                    auditorsHTML.push(`<li class="list-group-item d-flex justify-content-between align-items-center">
+                        <span class="mr-1"><i class="fa fa fa-stop-circle fa-rotate-90"></i></span>
+                    <span class="text-muted">${getGroupAuditHtml(group)}</span>
+                    <div class="d-flex ml-auto">
+                    ${getAuditTypeHtml(group[0].audit_type)}
+                    <span class="badge badge-light badge-pill ml-auto"><small>终审</small></span>
+                    </div>
+                    </li>`);
+                } else {
+                    auditorsHTML.push(`<li class="list-group-item d-flex justify-content-between align-items-center">
+                        <span class="mr-1"><i class="fa fa fa-chevron-circle-down"></i></span>
+                    <span class="text-muted">${getGroupAuditHtml(group)}</span>
+                    <div class="d-flex ml-auto">
+                    ${getAuditTypeHtml(group[0].audit_type)}
+                    <span class="badge badge-light badge-pill"><small>${transFormToChinese(idx)}审</small></span>
+                    </div>
+                    </li>`);
+                }
+            });
+            $('#auditor-list').empty();
+            $('#auditor-list').append(auditorsHTML.join(''));
+
+            let historyHTML = [];
+            auditHistory.forEach((his, idx) => {
+                if (idx === auditHistory.length - 1 && auditHistory.length !== 1) {
+                    historyHTML.push(`<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a></div>`);
+                }
+                historyHTML.push(`<div class="${idx < auditHistory.length - 1 ? 'fold-card' : ''}">`);
+                historyHTML.push(`<div class="text-center text-muted">${idx+1}#</div>`);
+                historyHTML.push(`<ul class="timeline-list list-unstyled mt-2 ${ idx === auditHistory.length - 1 && auditHistory.length !== 1 ? 'last-auditor-list' : '' }">`);
+                his.forEach((group, index) => {
+                    if (index === 0) {
+                        historyHTML.push(`<li class="timeline-list-item pb-2">
+                                            <div class="timeline-item-date">
+                                                ${group.beginYear}
+                                                <span>${group.beginDate}</span>
+                                                <span>${group.beginTime}</span>
+                                            </div>
+                                            <div class="timeline-item-tail"></div>
+                                            <div class="timeline-item-icon bg-success text-light"><i class="fa fa-caret-down"></i></div>
+                                            <div class="timeline-item-content">
+                                                <div class="py-1">
+                                                    <span class="text-black-50">原报</span>
+                                                    <span class="pull-right text-success">${idx !== 0 ? '重新' : '' }上报审批</span>
+                                                </div>
+                                                <div class="card">
+                                                    <div class="card-body px-3 py-0">
+                                                        <div class="card-text p-2 py-3 row">
+                                                            <div class="col">
+                                                                <span class="h6">${user.name}</span>
+                                                                <span class="text-muted ml-1">${user.role}</span>
+                                                            </div>
+                                                            <div class="col">
+                                                                <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </li>`);
+                    }
+                    historyHTML.push(`<li class="timeline-list-item pb-2 ${ group.status === auditConst.status.uncheck && idx === auditHistory.length - 1 && auditHistory.length !== 1 ? 'is_uncheck' : ''}">`);
+                    if (group.endYear) {
+                        historyHTML.push(`<div class="timeline-item-date">${group.endYear}<span>${group.endDate}</span><span>${group.endTime}</span></div>`);
+                    }
+                    if (index < his.length - 1) {
+                        historyHTML.push('<div class="timeline-item-tail"></div>');
+                    }
+                    if (group.status === auditConst.status.checked) {
+                        historyHTML.push('<div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>');
+                    } else if (group.status === auditConst.status.checkNo) {
+                        historyHTML.push('<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>');
+                    } else if (group.status === auditConst.status.checking) {
+                        historyHTML.push('<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>');
+                    } else if (group.status === auditConst.status.checkAgain) {
+                        historyHTML.push('<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-check"></i></div>');
+                    } else {
+                        historyHTML.push('<div class="timeline-item-icon bg-secondary text-light"></div>');
+                    }
+
+                    historyHTML.push('<div class="timeline-item-content">');
+                    historyHTML.push('<div class="py-1">');
+                    const statuStr = group.status !== auditConst.status.uncheck ?
+                        `<span class="pull-right ${auditConst.statusClass[group.status]}">${auditConst.statusString[group.status]}</span>` : '';
+                    historyHTML.push(`
+                    <span class="text-black-50">
+                    ${ group.audit_order === 0 ? '原报' : !group.is_final ? group.audit_order + '审' : '终审' } ${getAuditTypeText(group.audit_type)}
+                    </span>
+                    ${statuStr}`);
+                    historyHTML.push('</div>');
+                    historyHTML.push('<div class="card"><div class="card-body px-3 py-0">');
+                    for (const [i, auditor] of group.auditors.entries()) {
+                        historyHTML.push(`<div class="card-text p-2 py-3 row ${ ( i > 0 ? 'border-top' : '') }">`);
+                        historyHTML.push(`<div class="col"><span class="h6">${auditor.name}</span><span class="text-muted ml-1">${auditor.role}</span></div>`);
+                        historyHTML.push('<div class="col">');
+                        if (auditor.status === auditConst.status.checked) {
+                            historyHTML.push('<span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>');
+                        } else if (auditor.status === auditConst.status.checkNo) {
+                            historyHTML.push('<span class="pull-right text-warning"><i class="fa fa-share-square fa-rotate-270"></i></span>');
+                        } else if (auditor.status === auditConst.status.checking) {
+                            historyHTML.push('<span class="pull-right text-warning"><i class="fa fa-commenting"></i></span>');
+                        } else if (auditor.status === auditConst.status.checkAgain) {
+                            historyHTML.push('<span class="pull-right text-warning"><i class="fa fa-check"></i></span>');
+                        }
+                        historyHTML.push('</div>');
+                        if (auditor.opinion) {
+                            historyHTML.push(`<div class="col-12 py-1 bg-light"><i class="fa fa-commenting-o mr-1"></i>${auditor.opinion}</div>`);
+                        }
+                        historyHTML.push('</div>');
+                    }
+                    historyHTML.push('</div></div>');
+                    historyHTML.push('</div>');
+                    historyHTML.push('</li>');
+                });
+                historyHTML.push('</div>');
+                historyHTML.push('</ul>');
+            });
+            $('#audit-list').empty();
+            $('#audit-list').append(historyHTML.join(''));
+        });
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+});
+const getGroupAuditHtml = function (group) {
+    return group.map(u => { return `<small class="d-inline-block text-dark mx-1" title="${u.role}" data-auditorId="${u.aid}">${u.name}</small>`; }).join('');
+};
+
+const getAuditTypeHtml = function (type) {
+    if (type === auditType.key.common) return '';
+    return `<div class="li-subscript"><span class="badge badge-pill badge-${auditType.info[type].class} p-1 badge-bg-small"><small>${auditType.info[type].short}</small></span></div>`;
+};
+
+const getAuditTypeText = function (type) {
+    if (type === auditType.key.common) return '';
+    return `<span class="text-${auditType.info[type].class}">${auditType.info[type].long}</span>`;
+};

+ 848 - 0
app/public/js/financial_pay_stage.js

@@ -0,0 +1,848 @@
+'use strict';
+
+const tenderListSpec = (function(){
+    function getTenderNodeHtml(node, arr, pid) {
+        const html = [];
+        html.push('<tr pid="' + pid + '" data-tid="'+ (!node.cid ? node.id : -1) +'"' + (!node.cid ? 'class="change-tender" style="cursor: pointer;"' : '') + '>');
+        html.push('<td style="width: 60px" class="text-center">');
+        if (!node.cid) {
+            html.push(`<input type="checkbox" class="tender-check" name="tender_id[]" value="${node.id}" ${_.findIndex(tenders, { tid: node.id }) !== -1 ? 'checked disabled' : ''} ${is_admin || (fptReportTids && _.includes(fptReportTids, node.id)) ? '' : 'disabled' } />`);
+        }
+        html.push('</td>');
+        // 名称
+        html.push('<td class="in-' + node.level + '">');
+        if (node.cid) {
+            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ');
+            html.push((node.level === 1 ? '<b>' : ''), node.name, (node.level === 1 ? '</b>' : ''));
+        } else {
+            html.push('<span class="text-muted mr-2">');
+            html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
+            html.push('</span>');
+            html.push(node.name);
+        }
+        html.push('</td>');
+        html.push('</tr>');
+        return html.join('');
+    }
+    function getTenderTreeHeaderHtml() {
+        const html = [];
+        const left = $('#sub-menu').css('display') === 'none' ? 56 : 176;
+        html.push('<table class="table table-hover table-bordered" id="progress-table">')
+        html.push('<thead style="position: sticky;left:'+ left +'px;top: 0;">', '<tr>');
+        html.push('<th class="text-center" style="width: 60px;">', '选择', '</th>');
+        html.push('<th class="text-center">', '标段名称', '</th>');
+        html.push('</tr>', '</thead>');
+        return html.join('');
+    }
+    return { getTenderNodeHtml, getTenderTreeHeaderHtml }
+})();
+
+
+
+let auditUtils;
+$(function () {
+    autoFlashHeight();
+
+    $('#liucheng').on('shown.bs.modal', function () {
+        tenderListOrder.reOrderTenders();
+        initTenderTree();
+        $('#shenpi-tender-list').html(getTenderTreeHtml());
+        localHideList();
+        $('#shenpi-tender-list tr').removeClass('bg-warning');
+        if (tenders.length > 0) {
+            $('#shenpi-tender-list tr.change-tender').eq(0).addClass('bg-warning');
+            auditUtils.makeReportListHtml(tenders[0]);
+            auditUtils.makeShenpiListHtml(tenders[0]);
+        }
+    });
+
+    $('#payaccount').on('shown.bs.modal', function () {
+        tenderListOrder.reOrderTenders();
+        initTenderTree();
+        $('#pay-tender-list').html(getTenderTreeHtml());
+        localHideList();
+        $('#pay-tender-list tr').removeClass('bg-warning');
+        if (tenders.length > 0) {
+            $('#pay-tender-list tr.change-tender').eq(0).addClass('bg-warning');
+            auditUtils.makeBankHtml(tenders[0]);
+        }
+    });
+
+    $('#company_select').change(function () {
+        const company_id = parseInt($(this).val()) || 0;
+        setSelectValue('company', company_id);
+    });
+
+    $('#order_select').change(function () {
+        const qi = parseInt($(this).val()) || 0;
+        setSelectValue('qi', qi);
+    });
+
+    function setSelectValue(select, value) {
+        const routes = [];
+        const company_id = select === 'company' ? value : parseInt($('#company_select').val());
+        if (company_id) {
+            const companyInfo = _.find(userCompanyList, { id: company_id });
+            if (companyInfo) routes.push('company=' + companyInfo.name);
+        }
+        const qi = select === 'qi' ? value : parseInt($('#order_select').val());
+        if (qi) {
+            routes.push('qi=' + qi);
+        }
+        if (getLocalCache('account-pageSize')) {
+            routes.push('pageSize=' + getLocalCache('account-pageSize'));
+        }
+        window.location.href = `/sp/${spid}/financial/pay/stage` + (routes.length ? '?' + routes.join('&') : '');
+    }
+
+    let timer = null
+    let oldSearchVal = null
+    $('#liucheng').on('input propertychange', '.gr-search', function(e) {
+        oldSearchVal = e.target.value;
+        timer && clearTimeout(timer);
+        timer = setTimeout(() => {
+            const newVal = $(this).val();
+            const code = $(this).attr('data-code');
+            let html = '';
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                });
+                $('#' + code + '_dropdownMenu .book-list').empty();
+                $('#' + code + '_dropdownMenu .book-list').append(html);
+            } else {
+                if (!$('#' + code + '_dropdownMenu .acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return;
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`;
+                        group.groupList.forEach(item => {
+                            html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                        class="ml-auto">${item.mobile || ''}</span></p>
+                                <span class="text-muted">${item.role || ''}</span>
+                            </dd>`;
+                        });
+                        html += '</div>';
+                    });
+                    $('#' + code + '_dropdownMenu .book-list').empty();
+                    $('#' + code + '_dropdownMenu .book-list').append(html);
+                }
+            }
+        }, 400);
+    });
+
+    $('body').on('click', '#shenpi-tender-list .change-tender', function () {
+        if ($(this).hasClass('bg-warning')) {
+            return;
+        }
+        $('#shenpi-tender-list tr').removeClass('bg-warning');
+        $(this).addClass('bg-warning');
+        const tid = parseInt($(this).data('tid')) || 0;
+        const tender = tenders.find(t => t.id === tid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        auditUtils.makeReportListHtml(tender);
+        auditUtils.makeShenpiListHtml(tender);
+    });
+
+    $('body').on('click', '#pay-tender-list .change-tender', function () {
+        if ($(this).hasClass('bg-warning')) {
+            return;
+        }
+        $('#pay-tender-list tr').removeClass('bg-warning');
+        $(this).addClass('bg-warning');
+        const tid = parseInt($(this).data('tid')) || 0;
+        const tender = tenders.find(t => t.id === tid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        auditUtils.makeBankHtml(tender);
+    });
+    auditUtils = {
+        makeReportListHtml: function (flow) {
+            let addHtml = '';
+            $('#select-all-ptAudits').prop('checked', false);
+            for (const pl of flow.permissionList) {
+                addHtml += `<tr>
+                                <td class="text-center"><input type="checkbox" class="select-ptAudit" data-id="${pl.id}"></td><td>${pl.name}</td><td>${pl.company}</td>
+                                <td class="text-center"><input type="checkbox" class="save-report" data-id="${pl.id}" ${pl.is_report ? 'checked' : ''}></td><td class="text-center"><a href="javascript:void(0);" class="text-danger remove-audit" data-id="${pl.id}">移除</a></td>
+                            </tr>`;
+            }
+            $('#report-list').html(addHtml);
+        },
+        makeShenpiListHtml: function (flow) {
+            let addhtml = '<ul class="list-unstyled">\n';
+            addhtml += this.getgdsplHtml(flow, 'financial');
+            addhtml += '</ul>\n';
+            $('#shenpi-list').html(addhtml);
+        },
+        makeBankHtml: function (tender) {
+            $('#payaccount input[name="name"]').val('');
+            $('#payaccount input[name="bank"]').val('');
+            $('#payaccount input[name="bank_account"]').val('');
+            $('#payaccount input[name="contact"]').val('');
+            $('#payaccount input[name="phone"]').val('');
+            if (tender) {
+                $('#payaccount input[name="name"]').val(tender.pt.name);
+                $('#payaccount input[name="bank"]').val(tender.pt.bank);
+                $('#payaccount input[name="bank_account"]').val(tender.pt.bank_account);
+                $('#payaccount input[name="contact"]').val(tender.pt.contact);
+                $('#payaccount input[name="phone"]').val(tender.pt.phone);
+                if (is_admin || (fptReportTids && _.includes(fptReportTids, tender.id))) {
+                    $('#payaccount table input[type="text"]').attr('readonly', false);
+                    $('#get-form-tender').show();
+                    $('#batch-other-bank').show();
+                } else {
+                    $('#payaccount table input[type="text"]').attr('readonly', true);
+                    $('#get-form-tender').hide();
+                    $('#batch-other-bank').hide();
+                }
+            }
+        },
+        getAuditHtml: function(audit, is_report = 0) {
+            return '<span class="d-inline-block"><span class="badge badge-light">'+ audit.name +' <span class="dropdown">\n' +
+                '                                                            <a href="javascript:void(0);" class="btn-sm text-danger px-1" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-remove"></i></a>\n' +
+                '                                                            <div class="dropdown-menu">\n' +
+                '                                                                <a class="dropdown-item" href="javascript:void(0);">确认移除' + ( is_report ? '填报人' : '审批人') + '?</a>\n' +
+                '                                                                <div class="dropdown-divider"></div>\n' +
+                '                                                                <div class="px-2 py-1 text-center">\n' +
+                '                                                                    <button class="remove-audit btn btn-sm btn-danger" data-id="' + (is_report ? audit.id : audit.audit_id) + '">移除</button>\n' +
+                '                                                                    <button class="btn btn-sm btn-secondary">取消</button>\n' +
+                '                                                                </div>\n' +
+                '                                                            </div>\n' +
+                '                                                        </span> ' +
+                '                                            </span></span>\n'
+        },
+        getAuditTypeHtml: function(code, type) {
+            const html = [];
+            html.push(`<span class="d-inline-block"><select class="form-control form-control-sm audit-type-key" data-type="${type}">`);
+            for (const t of auditType.types) {
+                if (t.valid && t.valid.indexOf(code) < 0) continue;
+                html.push(`<option value="${t.value}" ${t.value === type ? 'selected' : ''}>${t.name}</option>`);
+            }
+            html.push('</select></span> ');
+            return html.join('');
+        },
+        getSelectAuditHtml: function (code, is_report = 0) {
+            let divhtml = '';
+            accountGroup.forEach((group, idx) => {
+                let didivhtml = '';
+                if(group) {
+                    group.groupList.forEach(item => {
+                        didivhtml += '<dd class="border-bottom p-2 mb-0 " data-id="' + item.id + '" >\n' +
+                            '<p class="mb-0 d-flex"><span class="text-primary">' + item.name + '</span><span\n' +
+                            '                                                                                class="ml-auto">' + item.mobile + '</span></p>\n' +
+                            '                                                                    <span class="text-muted">' + item.role + '</span>\n' +
+                            '                                                                    </dd>\n';
+                    });
+                    divhtml += '<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="' + idx + '" data-type="hide"><i class="fa fa-plus-square"></i></a> ' + group.groupName + '</dt>\n' +
+                        '                                                                <div class="dd-content" data-toggleid="' + idx + '">\n' + didivhtml +
+                        '                                                                </div>\n';
+                }
+            });
+            const html =
+                '                                            <span class="d-inline-block">\n' +
+                '                                                <div class="dropdown text-right">\n' +
+                '                                                    <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="' + code + '_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n' +
+                '                                                        ' + (is_report ? '添加填报人' : '选择审批人') + '\n' +
+                '                                                    </button>\n' +
+                '                                                    <div class="dropdown-menu dropdown-menu-right" id="' + code + '_dropdownMenu" aria-labelledby="' + code + '_dropdownMenuButton" style="width:220px">\n' +
+                '                                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"\n' +
+                '                                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="' + code + '"></div>\n' +
+                '                                                        <dl class="list-unstyled book-list">\n' + divhtml +
+                '                                                        </dl>\n' +
+                '                                                    </div>\n' +
+                '                                                </div>\n' +
+                '                                            </span>\n';
+            return html;
+        },
+        // 以下i从1开始
+        getAuditGroupInnerHtml: function(code, auditGroup, i) {
+            const html = [];
+            const type = auditGroup.length > 0 ? auditGroup[0].audit_type : auditType.key.common;
+            html.push(`<span class="col-auto">${transFormToChinese(i)}审</span><span class="col-10 spr-span">`);
+            html.push(this.getAuditTypeHtml(code, type));
+            for (const audit of auditGroup) {
+                html.push(this.getAuditHtml(audit));
+            }
+            if (type !== auditType.key.common || auditGroup.length === 0) {
+                html.push(this.getSelectAuditHtml(code));
+            }
+            if (type === auditType.key.union && auditGroup.length > 0) {
+                html.push(`<button class="btn btn-sm btn-outline-primary" sp_type="${code}" audit_order="${i}" name="union-set">协同设置</button>`);
+            }
+            html.push('</span>');
+            return html.join('');
+        },
+        getAuditGroupHtml: function (code, auditGroup, i) {
+            return `<li class="d-flex justify-content-start align-items-center mb-3">${this.getAuditGroupInnerHtml(code, auditGroup, i)}</li>`;
+        },
+        getgdsplHtml(flow, this_code) {
+            let addhtml = '';
+            if (flow.auditGroupList.length !== 0) {
+                for(const [i, auditGroup] of flow.auditGroupList.entries()) {
+                    addhtml += this.getAuditGroupHtml(this_code, auditGroup, i + 1);
+                }
+                const addGroupHtml = this_code === 'change' && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
+                    `<span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="${this_code}"><i class="fa fa-save"></i> 存为审批组</a></span>\n` : '';
+                addhtml += '<li>\n' +
+                    '                                            <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>\n' + addGroupHtml +
+                    '                                        </li>';
+            } else {
+                addhtml += this.getAuditGroupHtml(this_code, [], 1);
+            }
+            return addhtml;
+        },
+        // 以下i从0开始
+        addAudit: function (tender, user, i) {
+            const flow = tender;
+            if (!flow.auditGroupList) flow.auditGroupList = [];
+            if (!flow.auditGroupList[i]) flow.auditGroupList[i] = [];
+            flow.auditGroupList[i].push(user);
+            return flow.auditGroupList[i];
+        },
+        removeAudit: function (tender, audit_id, i) {
+            const flow = tender;
+            if (flow.auditGroupList[i].length === 1) {
+                flow.auditGroupList.splice(i, 1);
+                return null;
+            }
+            flow.auditGroupList[i].splice(flow.auditGroupList[i].findIndex(x => { return x.audit_id === audit_id; }), 1);
+            return flow.auditGroupList[i];
+        },
+        setAuditType: function (tender, audit_type, i) {
+            const flow = tender;
+            if (!flow || !flow.auditGroupList || !flow.auditGroupList[i]) return;
+            flow.auditGroupList[i].forEach(x => { x.audit_type = audit_type});
+            return flow.auditGroupList[i];
+        }
+    };
+
+    // 选中填报人
+    $('body').on('click', 'div[id="report_audit_dropdownMenu"] dl dd', function () {
+        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
+        const tender = tenders.find(t => t.id === tid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        const id = parseInt($(this).data('id'));
+        if (!id) return;
+        if (!isNaN(id) && id !== 0) {
+            postData(`/sp/${spid}/financial/pay/stage/save`, {type: 'add-tender-audit', id: id, tid: tender.id }, function (result) {
+                tender.permissionList = result;
+                auditUtils.makeReportListHtml(tender);
+            })
+        }
+    });
+
+    // 移除填报人
+    $('body').on('click', '#report-list .remove-audit', function () {
+        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
+        const tender = tenders.find(t => t.id === tid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        const id = parseInt($(this).data('id'));
+        deleteAfterHint(function () {
+            postData(`/sp/${spid}/financial/pay/stage/save`, {type: 'del-tender-audit', id, tid: tender.id }, function (result) {
+                tender.permissionList = result.permissionList;
+                tender.auditGroupList = result.auditGroupList;
+                auditUtils.makeReportListHtml(tender);
+                auditUtils.makeShenpiListHtml(tender);
+            });
+        }, '确认删除该标段用户?');
+    });
+
+    // 勾选是否为填报人
+    $('body').on('click', '#report-list .save-report', function () {
+        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
+        const tender = tenders.find(t => t.id === tid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        const id = parseInt($(this).data('id'));
+        const isReport = $(this).prop('checked');
+        const permission = tender.permissionList.find(p => p.id === id);
+        if (!permission) {
+            toastr.error('该用户不存在');
+            return;
+        }
+        postData(`/sp/${spid}/financial/pay/stage/save`, {type: 'save-permission', updateData: { id, is_report: isReport } }, function (result) {
+            permission.is_report = isReport;
+        });
+    });
+
+    $('#select-all-ptAudits').click(function () {
+        $('#report-list .select-ptAudit').prop('checked', $(this).prop('checked'));
+    });
+
+    $('#batch-del-ptAudit').click(function () {
+        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
+        const tender = tenders.find(t => t.id === tid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        const ids = [];
+        $('#report-list .select-ptAudit:checked').each(function () {
+            ids.push(parseInt($(this).data('id')));
+        });
+        if (ids.length === 0) {
+            toastr.warning('请勾选要删除的用户');
+            return;
+        }
+        deleteAfterHint(function () {
+            postData(`/sp/${spid}/financial/pay/stage/save`, {type: 'del-tender-audit', id: ids, tid: tender.id }, function (result) {
+                tender.permissionList = result.permissionList;
+                tender.auditGroupList = result.auditGroupList;
+                auditUtils.makeReportListHtml(tender);
+                auditUtils.makeShenpiListHtml(tender);
+            });
+        }, '确认删除已勾选的标段用户?');
+    });
+
+    $('#batch-other-ptAudit').click(function () {
+        const cur_tenderid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
+        const tender = tenders.find(t => t.id === cur_tenderid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        const ids = [];
+        $('#report-list .select-ptAudit:checked').each(function () {
+            ids.push(parseInt($(this).data('id')));
+        });
+        if (ids.length === 0) {
+            toastr.warning('请勾选要同步的用户');
+            return;
+        }
+        const num = $('#shenpi-tender-list input:checked').length;
+        if (num === 0 || (num === 1 && parseInt($('#shenpi-tender-list input:checked').eq(0).parents('tr').data('tid')) === cur_tenderid)) {
+            toastr.warning('请选择需要设置审批同步的标段');
+            return;
+        }
+        const tenderList = [];
+        for (let i = 0; i < num; i++) {
+            const tid = parseInt($('#shenpi-tender-list input:checked').eq(i).parents('tr').data('tid'));
+            if (tid !== cur_tenderid) {
+                tenderList.push(tid);
+            }
+        }
+        const data = {
+            type: 'copy-tender-audit',
+            id: ids,
+            this_tid: tender.id,
+        }
+        data.tidList = tenderList.join(',');
+        postData(`/sp/${spid}/financial/pay/stage/save`,  data, function (result) {
+            toastr.success('已同步到其它勾选标段');
+            for (let i = 0; i < num; i++) {
+                const tid = parseInt($('#shenpi-tender-list input:checked').eq(i).parents('tr').data('tid'));
+                if (tid !== cur_tenderid) {
+                    const other_tender = tenders.find(t => t.id === tid);
+                    const permissionList = _.filter(result.otherPermissionList, { tid: tid });
+                    other_tender.permissionList = permissionList;
+                }
+            }
+        });
+    });
+
+    $('#batch-other-bank').click(function () {
+        const cur_tenderid = parseInt($('#pay-tender-list tr.bg-warning').data('tid'));
+        const tender = tenders.find(t => t.id === cur_tenderid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        const copyTenders = [];
+        $('#pay-tender-list input:checked').each(function () {
+            if (cur_tenderid !== parseInt($(this).val())) {
+                const one = tenders.find(t => t.id === parseInt($(this).val()));
+                copyTenders.push(one.pt);
+            }
+        });
+        if (copyTenders.length === 0) {
+            toastr.warning('请勾选要同步的标段');
+            return;
+        }
+        const data = {
+            type: 'copy-tender-bank',
+            tenders: copyTenders,
+            this_tender: tender.pt,
+        }
+        postData(`/sp/${spid}/financial/pay/stage/save`,  data, function (result) {
+            toastr.success('已同步信息到其它勾选标段');
+            $('#pay-tender-list input:checked').each(function () {
+                if (cur_tenderid !== parseInt($(this).val())) {
+                    const one = tenders.find(t => t.id === parseInt($(this).val()));
+                    const newOne = result.find(t => t.tid === one.id);
+                    if (newOne) {
+                        one.pt = newOne;
+                    }
+                }
+            });
+        });
+    });
+
+    // 选中审批人
+    $('body').on('click', '#shenpi-list div[id$="_dropdownMenu"] dl dd', function () {
+        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
+        const tender = tenders.find(t => t.id === tid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        const id = parseInt($(this).data('id'));
+        if (!id) return;
+
+        let this_code = 'financial';
+        const user = _.find(accountList, function (item) {
+            return item.id === id;
+        });
+        // 判断是否已存在审批人
+        const aid_num = $(this).parents('ul').find('.remove-audit').length;
+        for (let i = 0; i < aid_num; i++) {
+            const aid = parseInt($(this).parents('ul').find('.remove-audit').eq(i).data('id'));
+            if (aid === id) {
+                toastr.warning('该审核人已存在,请勿重复添加');
+                return;
+            }
+        }
+        const prop = {
+            status: sp_status.gdspl,
+            code: sp_type[this_code],
+            audit_id: id,
+            type: 'add',
+            audit_type: parseInt($(this).parents('li').find('select[class*="audit-type-key"]')[0].value),
+            audit_order: $(this).parents('li').index() + 1,
+        };
+        const _self = $(this);
+        postData(`/sp/${spid}/financial/pay/stage/save`, { type: 'add-shenpi-audit', shenpi: prop, tid }, function (result) {
+            const data = result.shenpi;
+            const auditGroup = auditUtils.addAudit(tender, { audit_id: data.audit_id, name: user.name, audit_type: data.audit_type, audit_order: data.audit_order }, prop.audit_order - 1);
+            if (_self.parents('ul').find('.add-audit').length === 0) {
+                _self.parents('ul').append('<li>\n' +
+                    '                                            <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>\n' +
+                    '                                        </li>');
+            }
+            _self.parents('li').html(auditUtils.getAuditGroupInnerHtml(this_code, auditGroup, prop.audit_order));
+            tender.permissionList = result.permissionList;
+            auditUtils.makeReportListHtml(tender);
+        });
+    });
+
+    // 移除审批人
+    $('body').on('click', '#shenpi-list .remove-audit', function () {
+        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
+        const tender = tenders.find(t => t.id === tid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        const id = parseInt($(this).data('id'));
+        const this_code = 'financial';
+        const prop = {
+            status: sp_status.gdspl,
+            code: sp_type[this_code],
+            audit_id: id,
+            type: 'del',
+        };
+        const _self = $(this);
+        postData('/tender/' + tid + '/shenpi/audit/save', prop, function (data) {
+            const index = _self.parents('li').index();
+            const auditGroup = auditUtils.removeAudit(tender, id, index);
+            if (auditGroup) {
+                _self.parents('li').html(auditUtils.getAuditGroupInnerHtml(this_code, auditGroup, index + 1));
+            } else {
+                const _selflc = _self.parents('#shenpi-list');
+                _self.parents('li').remove();
+                const aid_num = parseInt(_selflc.children('ul').find('li.d-flex').length);
+                if (aid_num === 0) {
+                    _selflc.children('ul').html(auditUtils.getAuditGroupHtml(this_code, [], 1));
+                } else {
+                    for (let i = 0; i < aid_num; i++) {
+                        _selflc.find('li.d-flex').eq(i).find('.col-auto').text(transFormToChinese(i+1) + '审');
+                    }
+                }
+            }
+        })
+    });
+
+    $('body').on('click', '#shenpi-list .add-audit', function () {
+        const num = $(this).parents('ul').children('li').length;
+        const this_code = 'financial';
+        const addhtml = auditUtils.getAuditGroupHtml(this_code, [], num);
+        $(this).parents('ul').append(addhtml);
+        $(this).parents('li').remove();
+    });
+
+    // 设置会签、或签
+    $('body').on('change', 'select[class*="audit-type-key"]', function() {
+        const tid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
+        const tender = tenders.find(t => t.id === tid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        const removes = $(this).parents('.d-flex').find('.remove-audit');
+        if (removes.length === 0) return;
+
+        const this_status = sp_status.gdspl;
+        const this_code = 'financial';
+        const ids = [];
+        const liParent = $(this).parents('li');
+        removes.each((i, r) => { ids.push(parseInt(r.getAttribute('data-id'))); });
+        const prop = {
+            status: this_status,
+            code: sp_type[this_code],
+            audit_id: ids,
+            audit_type: parseInt(this.value),
+            type: 'audit-type',
+        };
+        if (prop.audit_type === auditType.key.common && ids.length > 1) {
+            toastr.warning('设置个人审批前请先删除多余的审批人');
+            this.value = this.getAttribute('data-type');
+            return;
+        }
+        const _self = this;
+        postData('/tender/'+ tid +'/shenpi/audit/save', prop, function () {
+            _self.setAttribute('data-type', _self.value);
+            const auditGroup = auditUtils.setAuditType(tender, prop.audit_type, liParent.index());
+            liParent.html(auditUtils.getAuditGroupInnerHtml(this_code, auditGroup, liParent.index() + 1));
+        });
+    });
+
+    $('#set-other-tenders').on('click', function () {
+        const cur_tenderid = parseInt($('#shenpi-tender-list tr.bg-warning').data('tid'));
+        const tender = tenders.find(t => t.id === cur_tenderid);
+        if (!tender) {
+            toastr.error('请选择标段');
+            return;
+        }
+        if (tender.auditGroupList.length === 0) {
+            toastr.warning('请先设置审批流程再同步到其它标段');
+            return;
+        }
+        const this_code = 'financial';
+        const num = $('#shenpi-tender-list input:checked').length;
+        if (num === 0 || (num === 1 && parseInt($('#shenpi-tender-list input:checked').eq(0).parents('tr').data('tid')) === cur_tenderid)) {
+            toastr.warning('请选择需要设置审批同步的标段');
+            return;
+        }
+        const data = {
+            type: 'copy-shenpi-audit',
+            status: sp_status.gdspl,
+            code: this_code,
+            this_tid: cur_tenderid,
+        };
+        // 二维数组变一维
+        data.auditList = [];
+        for (const auditGroup of tender.auditGroupList) {
+            data.auditList.push(...auditGroup);
+        }
+        const tenderList = [];
+        for (let i = 0; i < num; i++) {
+            const tid = parseInt($('#shenpi-tender-list input:checked').eq(i).parents('tr').data('tid'));
+            if (tid !== cur_tenderid) {
+                tenderList.push(tid);
+            }
+        }
+        data.tidList = tenderList.join(',');
+        console.log(data);
+        postData(`/sp/${spid}/financial/pay/stage/save`, data, function (result) {
+            toastr.success('已同步到其它勾选标段');
+            for (let i = 0; i < num; i++) {
+                const tid = parseInt($('#shenpi-tender-list input:checked').eq(i).parents('tr').data('tid'));
+                if (tid !== cur_tenderid) {
+                    const other_tender = tenders.find(t => t.id === tid);
+                    other_tender.auditGroupList = tender.auditGroupList;
+                    const permissionList = _.filter(result.otherPermissionList, { tid: tid });
+                    other_tender.permissionList = permissionList;
+                }
+            }
+        });
+    });
+
+    // 添加到成员中
+    $('body').on('click', '.book-list dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid')
+        const type = $(this).find('.acc-btn').attr('data-type')
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                $(this).find('.acc-btn').attr('data-type', 'show')
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                $(this).find('.acc-btn').attr('data-type', 'hide')
+            })
+        }
+        return false
+    });
+
+
+    $('#add-company-select').on('change', function () {
+        const company = userCompanyList.find(t => t.name === $(this).val());
+        $('#company_order').val(company ? company.count + 1 : 0);
+    });
+
+    $('#add-qi-btn').on('click', function () {
+        const companyName = $('#add-company-select').val();
+        const company = userCompanyList.find(t => t.name === companyName);
+        if (!company) {
+            toastr.error('单位不存在');
+            return;
+        }
+        const order = parseInt($('#company_order').val());
+        if (!order || order < 1) {
+            toastr.error('期数有误');
+            return;
+        }
+        const prop = {
+            company_id: company.id,
+            order,
+        };
+        postData(`/sp/${spid}/financial/pay/stage/save`, { type: 'add-pay-stage', updateData: prop }, function (result) {
+            window.location.href = `/sp/${spid}/financial/pay/stage/${result.id}`;
+        });
+    });
+
+    $('body').on('click', '#pay-list .del-pay-btn', function () {
+        const fpsid = $(this).data('id');
+        deleteAfterHint(function () {
+            postData(`/sp/${spid}/financial/pay/stage/save`, {type: 'del-pay-stage', postData: { node: fpsid }}, function (result) {
+                window.location.reload();
+            })
+        }, '确认删除该资金支付单位期?');
+    });
+
+    $('#get-form-tender').on('click', function () {
+        const tid = $('#pay-tender-list tr.bg-warning').data('tid');
+        const tender = tenders.find(t => t.id === parseInt(tid));
+        if (!tid || !tender) {
+            toastr.error('标段不存在');
+            return;
+        }
+        if (is_admin || (fptReportTids && _.includes(fptReportTids, tender.id))) {
+            console.log(tender);
+            if (tender.pay_account && (tender.pay_account.name || tender.pay_account.bank || tender.pay_account.account || tender.pay_account.contact || tender.pay_account.phone)) {
+                $('#payaccount input[name="name"]').val(tender.pay_account.name);
+                $('#payaccount input[name="bank"]').val(tender.pay_account.bank);
+                $('#payaccount input[name="bank_account"]').val(tender.pay_account.account);
+                $('#payaccount input[name="contact"]').val(tender.pay_account.contact);
+                $('#payaccount input[name="phone"]').val(tender.pay_account.phone);
+                const data = {
+                    id: tender.pt.id,
+                    tid: tender.id,
+                    name: tender.pay_account.name,
+                    bank: tender.pay_account.bank,
+                    bank_account: tender.pay_account.account,
+                    contact: tender.pay_account.contact,
+                    phone: tender.pay_account.phone,
+                }
+                postData(`/sp/${spid}/financial/pay/stage/save`, { type: 'set-pay-tender', updateData: data }, function (result) {
+                    toastr.success('已同步');
+                    tender.pt = result;
+                });
+            } else {
+                toastr.success('已同步');
+            }
+        } else {
+            toastr.error('无权限操作');
+            return;
+        }
+    });
+
+    $('#payaccount input[type="text"]').on('change', function () {
+        const tid = $('#pay-tender-list tr.bg-warning').data('tid');
+        const tender = tenders.find(t => t.id === parseInt(tid));
+        if (!tid || !tender) {
+            toastr.error('标段不存在');
+            return;
+        }
+        if (is_admin || (fptReportTids && _.includes(fptReportTids, tender.id))) {
+            const data = {
+                id: tender.pt.id,
+                tid: tender.id,
+                name: $('#payaccount input[name="name"]').val(),
+                bank: $('#payaccount input[name="bank"]').val(),
+                bank_account: $('#payaccount input[name="bank_account"]').val(),
+                contact: $('#payaccount input[name="contact"]').val(),
+                phone: $('#payaccount input[name="phone"]').val(),
+            }
+            postData(`/sp/${spid}/financial/pay/stage/save`, { type: 'set-pay-tender', updateData: data }, function (result) {
+                tender.pt = result;
+            });
+        }
+    });
+
+    $('#set-pay-btn').on('click', function () {
+        const tid = $('#payaccount input[name="tid"]').val();
+        const tender = tenders.find(t => t.id === parseInt(tid));
+        if (!tid || !tender) {
+            toastr.error('标段不存在');
+            return;
+        }
+        if (is_admin || (fptReportTids && _.includes(fptReportTids, tender.id))) {
+            const data = {
+                id: parseInt($('#payaccount input[name="id"]').val()),
+                tid: tender.id,
+                name: $('#payaccount input[name="name"]').val(),
+                bank: $('#payaccount input[name="bank"]').val(),
+                bank_account: $('#payaccount input[name="bank_account"]').val(),
+                contact: $('#payaccount input[name="contact"]').val(),
+                phone: $('#payaccount input[name="phone"]').val(),
+            }
+            if (!data.name) {
+                toastr.error('请填写开户名称');
+                return;
+            }
+            if (!data.bank) {
+                toastr.error('请填写开户银行');
+                return;
+            }
+            if (!data.bank_account) {
+                toastr.error('请填写开户账号');
+                return;
+            }
+            postData(`/sp/${spid}/financial/pay/stage/save`, { type: 'set-pay-tender', updateData: data }, function (result) {
+                toastr.success('保存成功');
+                tender.pt = result;
+            });
+        } else {
+            toastr.error('无权限操作');
+            return;
+        }
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+});

+ 365 - 22
app/public/js/financial_transfer_tender.js

@@ -1,20 +1,203 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/10/11
+ * @version
+ */
+
+const tenderListSpec = (function(){
+    function getTenderNodeHtml(node, arr, pid) {
+        const html = [];
+        html.push('<tr pid="' + pid + '">');
+        // 名称
+        html.push('<td width="30%" style="min-width: 300px;" class="in-' + node.level + '">');
+        if (node.cid) {
+            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ');
+            html.push((node.level === 1 ? '<b>' : ''), node.name, (node.level === 1 ? '</b>' : ''));
+        } else {
+            html.push('<span class="text-muted mr-2">');
+            html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
+            html.push('</span>');
+            html.push(node.name);
+        }
+        html.push('</td>');
+        // 计量进度
+        html.push('<td style="width: 130px" class="text-center">');
+        if (!node.cid && node.cur_flow) {
+            if (node.progress) {
+                html.push(node.progress.title + ' (' + '<span class="' + node.progress.status_class + '">' + node.progress.status + '</span>' + ')');
+            } else {
+                html.push(node.cur_flow.title + ' (' + '<span class="' + node.cur_flow.status_class + '">' + node.cur_flow.status + '</span>' + ')');
+            }
+        }
+        html.push('</td>');
+        // 资金来源
+        html.push('<td style="width: 200px" class="text-center">');
+        if (!node.cid) {
+            html.push(transferInfo.uid === user_id && !transferInfo.is_lock ? '<input type="text" data-key="source" class="form-control form-control-sm" placeholder="" data-ftid="' + node.id + '" value="'+ node.source +'" />' : node.source);
+        }
+        html.push('</td>');
+        // 合同金额
+        html.push('<td style="width: 100px" class="text-right">');
+        html.push(node.total_price || '');
+        html.push('</td>');
+        // 截止本期合同
+        html.push('<td style="width: 100px" class="text-right">');
+        html.push(node.end_contract_tp || '');
+        html.push('</td>');
+        // 截止本期变更
+        html.push('<td style="width: 100px" class="text-right">');
+        html.push(node.end_qc_tp || '');
+        html.push('</td>');
+        // 截止本期完成
+        html.push('<td style="width: 100px" class="text-right">');
+        html.push(node.end_gather_tp || '');
+        html.push('</td>');
+        // 截止本期实付
+        html.push('<td style="width: 100px" class="text-right">');
+        html.push(node.end_sf_tp || '');
+        html.push('</td>');
+        // 本次划拨
+        html.push('<td style="width: 100px" class="text-right">');
+        if (!node.cid) {
+            html.push(transferInfo.uid === user_id && !transferInfo.is_lock ? '<input type="text" data-key="hb_tp" class="form-control form-control-sm text-right" placeholder="默认等于本期实付" data-ftid="' + node.id + '" value="' + node.hb_tp + '" />' : node.hb_tp);
+        } else {
+            html.push(node.hb_tp || '');
+        }
+        html.push('</td>');
+        // 截止上次划拨
+        html.push('<td style="width: 100px" class="text-right">');
+        html.push(node.pre_hb_tp || '');
+        html.push('</td>');
+        // 截止本次划拨
+        html.push('<td style="width: 100px" class="text-right">');
+        html.push(node.end_hb_tp || '');
+        html.push('</td>');
+        // 附件
+        html.push('<td style="width: 80px" class="text-center">');
+        if (!node.cid) {
+            html.push('<a href="javascript:void(0);" class="text-primary open-tender-files" data-ftid="' + node.id + '"><i class="fa fa-paperclip fa-rotate-90"></i></a> <span class="file-num">'+ (node.files.length > 0 ? node.files.length : '') +'</span>');
+        }
+        html.push('</td>');
+        // 操作
+        if (transferInfo.uid === user_id) {
+            html.push('<td style="width: 80px" class="text-center">');
+            html.push(!node.cid && !transferInfo.is_lock ? '<a class="text-danger del-tender-btn" href="javascript:void(0);" data-id="'+ node.id +'">移除</a>' : '');
+            html.push('</td>');
+        }
+        html.push('</tr>');
+        return html.join('');
+    }
+    function getTenderTreeHeaderHtml() {
+        const html = [];
+        const left = $('#sub-menu').css('display') === 'none' ? 56 : 176;
+        html.push('<table class="table table-hover table-bordered" id="progress-table">')
+        html.push('<thead style="position: sticky;left:'+ left +'px;top: 0;">', '<tr>');
+        html.push('<th style="width: 30%;min-width: 300px" class="text-center">', '标段名称', '</th>');
+        html.push('<th class="text-center" style="width: 130px">', '计量进度', '</th>');
+        html.push('<th class="text-center" style="width: 200px">', '资金来源', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '合同金额', '</th>');
+        html.push('<th style="width: 100px" class="text-center">', '截止本期合同', '</th>');
+        html.push('<th style="width: 100px" class="text-center">', '截止本期变更', '</th>');
+        html.push('<th style="width: 100px" class="text-center">', '截止本期完成', '</th>');
+        html.push('<th style="width: 100px" class="text-center">', '截止本期实付', '</th>');
+        html.push('<th style="width: 100px" class="text-center">', '本次划拨', '</th>');
+        html.push('<th style="width: 100px" class="text-center">', '截止上次划拨', '</th>');
+        html.push('<th style="width: 100px" class="text-center">', '截止本次划拨', '</th>');
+        html.push('<th style="width: 80px" class="text-center">', '附件', '</th>');
+        if (transferInfo.uid === user_id) {
+            html.push('<th style="width: 80px" class="text-center">', '操作', '</th>');
+        }
+        html.push('</tr>', '</thead>');
+        return html.join('');
+    }
+    function calculateTender(tender) {
+        if (tender.stage_tp) {
+            tender.gather_tp = ZhCalc.sum([tender.stage_tp.contract_tp, tender.stage_tp.qc_tp, tender.stage_tp.pc_tp]);
+            tender.end_contract_tp = ZhCalc.sum([tender.stage_tp.pre_contract_tp, tender.stage_tp.contract_tp, tender.stage_tp.contract_pc_tp]);
+            tender.end_qc_tp = ZhCalc.sum([tender.stage_tp.pre_qc_tp, tender.stage_tp.qc_tp, tender.stage_tp.qc_pc_tp]);
+            tender.end_gather_tp = ZhCalc.add(tender.end_contract_tp, tender.end_qc_tp);
+            tender.pre_gather_tp = ZhCalc.add(tender.stage_tp.pre_contract_tp, tender.stage_tp.pre_qc_tp);
+            tender.yf_tp = ZhCalc.add(tender.stage_tp.yf_tp);
+            tender.end_yf_tp = ZhCalc.add(tender.stage_tp.pre_yf_tp, tender.yf_tp);
+            tender.sf_tp = ZhCalc.add(tender.stage_tp.sf_tp);
+            tender.end_sf_tp = ZhCalc.add(tender.stage_tp.pre_sf_tp, tender.sf_tp);
+            tender.wf_tp = ZhCalc.sub(tender.end_yf_tp, tender.end_sf_tp);
+        } else if (tender.lastStage) {
+            tender.gather_tp = ZhCalc.sum([tender.lastStage.contract_tp, tender.lastStage.qc_tp, tender.lastStage.pc_tp]);
+            tender.end_contract_tp = ZhCalc.sum([tender.lastStage.pre_contract_tp, tender.lastStage.contract_tp, tender.lastStage.contract_pc_tp]);
+            tender.end_qc_tp = ZhCalc.sum([tender.lastStage.pre_qc_tp, tender.lastStage.qc_tp, tender.lastStage.qc_pc_tp]);
+            tender.end_gather_tp = ZhCalc.add(tender.end_contract_tp, tender.end_qc_tp);
+            tender.pre_gather_tp = ZhCalc.add(tender.lastStage.pre_contract_tp, tender.lastStage.pre_qc_tp);
+            tender.yf_tp = ZhCalc.add(tender.lastStage.yf_tp);
+            tender.end_yf_tp = ZhCalc.add(tender.lastStage.pre_yf_tp, tender.yf_tp);
+            tender.sf_tp = ZhCalc.add(tender.lastStage.sf_tp);
+            tender.end_sf_tp = ZhCalc.add(tender.lastStage.pre_sf_tp, tender.sf_tp);
+            tender.wf_tp = ZhCalc.sub(tender.end_yf_tp, tender.end_sf_tp);
+        }
+        tender.end_hb_tp = ZhCalc.add(tender.pre_hb_tp, tender.hb_tp);
+    }
+    function calculateParent(node) {
+        if (node.children && node.cid) {
+            node.total_price = 0;
+            node.gather_tp = 0;
+            node.end_contract_tp = 0;
+            node.end_qc_tp = 0;
+            node.end_gather_tp = 0;
+            node.pre_gather_tp = 0;
+            node.yf_tp = 0;
+            node.end_yf_tp = 0;
+            node.advance_tp = 0;
+            node.contract_price = 0;
+            node.sf_tp = 0;
+            node.end_sf_tp = 0;
+            node.wf_tp = 0;
+            node.hb_tp = 0;
+            node.pre_hb_tp = 0;
+            node.end_hb_tp = 0;
+            for (const c of node.children) {
+                calculateParent(c);
+                node.total_price = ZhCalc.add(node.total_price, c.total_price);
+                node.gather_tp = ZhCalc.add(node.gather_tp, c.gather_tp);
+                node.end_contract_tp = ZhCalc.add(node.end_contract_tp, c.end_contract_tp);
+                node.end_qc_tp = ZhCalc.add(node.end_qc_tp, c.end_qc_tp);
+                node.end_gather_tp = ZhCalc.add(node.end_gather_tp, c.end_gather_tp);
+                node.pre_gather_tp = ZhCalc.add(node.pre_gather_tp, c.pre_gather_tp);
+                node.yf_tp = ZhCalc.add(node.yf_tp, c.yf_tp);
+                node.end_yf_tp = ZhCalc.add(node.end_yf_tp, c.end_yf_tp);
+                node.advance_tp = ZhCalc.add(node.advance_tp, c.advance_tp);
+                node.contract_price = ZhCalc.add(node.contract_price, c.contract_price);
+                node.sf_tp = ZhCalc.add(node.sf_tp, c.sf_tp);
+                node.end_sf_tp = ZhCalc.add(node.end_sf_tp, c.end_sf_tp);
+                node.wf_tp = ZhCalc.add(node.wf_tp, c.wf_tp);
+                node.hb_tp = ZhCalc.add(node.hb_tp, c.hb_tp);
+                node.pre_hb_tp = ZhCalc.add(node.pre_hb_tp, c.pre_hb_tp);
+                node.end_hb_tp = ZhCalc.add(node.end_hb_tp, c.end_hb_tp);
+            }
+        }
+    }
+    return { getTenderNodeHtml, getTenderTreeHeaderHtml, calculateTender, calculateParent }
+})();
+
 $(function () {
     autoFlashHeight();
 
     $('#add-tender-btn').click(function () {
         const addTenders = [];
-        $('#tenders tr').each(function () {
-            if ($(this).find('input').is(':checked') && $(this).find('select').val().length > 0) {
-                addTenders.push({
-                    tid: $(this).find('input').val(),
-                    sorder: $(this).find('select').val(),
-                })
+        $('input[name="tender_id[]"]:checked:not(:disabled)').each(function () {
+            const tenderId = $(this).val();
+            if (tenderId) {
+                addTenders.push(parseInt(tenderId));
             }
         });
         if (addTenders.length === 0) {
-            toastr.warning('请选择添加标段及期数');
+            toastr.warning('请选择添加标段');
             return false;
         }
+        console.log(addTenders);
         postData(window.location.pathname + '/update', {postType: 'add-tender', postData: { tenders: addTenders }}, function (result) {
             window.location.reload();
         });
@@ -41,28 +224,48 @@ $(function () {
         });
     });
 
-    $('#tender-list input[type="text"]').change(function () {
+    $('body').on('change', '#tender-list input[type="text"]', function () {
         const ftid = $(this).data('ftid');
         if (!ftid) {
             toastr.error('获取资金划拨标段信息失败');
             return;
         }
-        const ftInfo = _.find(tenderList, { id: parseInt(ftid) });
+        const ftInfo = _.find(tenders, { id: parseInt(ftid) });
         if (!ftInfo) {
             toastr.error('获取资金划拨信息失败');
             return;
         }
-        const value = $(this).val() || 0;
-        // 判断只能输入数字,支持负数和小数
-        if (!/^-?\d+(\.\d+)?$/.test(value)) {
-            toastr.error('请输入正确的金额');
-            $(this).val(ftInfo.hb_tp);
-            return;
+        const key = $(this).data('key');
+        if (key === 'hb_tp') {
+            const value = $(this).val() || 0;
+            // 判断只能输入数字,支持负数和小数
+            if (!/^-?\d+(\.\d+)?$/.test(value)) {
+                toastr.error('请输入正确的金额');
+                $(this).val(ftInfo.hb_tp);
+                return;
+            }
+            postData(window.location.pathname + '/update', {postType: 'update-hb_tp', postData: { node: ftid, hb_tp: parseFloat(value) }}, function (result) {
+                // window.location.reload();
+                ftInfo.hb_tp = parseFloat(value);
+                // 更新并汇总父节点和截止本次划拨汇总
+                ftInfo.end_hb_tp = ZhCalc.add(ftInfo.pre_hb_tp, ftInfo.hb_tp);
+                for (const t of tenderTree) {
+                    tenderListSpec.calculateParent(t);
+                }
+                $('.c-body').html(getTenderTreeHtml());
+            });
+        } else {
+            const value = $(this).val() || '';
+            if (value.length > 255) {
+                toastr.error('输入内容过长,不能超过255个字符');
+                $(this).val(ftInfo[key]);
+                return;
+            }
+            postData(window.location.pathname + '/update', {postType: 'update-tender', postData: { node: ftInfo.id, key: key, value: value }}, function (result) {
+                // window.location.reload();
+                ftInfo[key] = value;
+            });
         }
-        postData(window.location.pathname + '/update', {postType: 'update-hb_tp', postData: { node: ftid, hb_tp: parseFloat(value) }}, function (result) {
-            // window.location.reload();
-            ftInfo.hb_tp = parseFloat(value);
-        });
     });
 
     $('body').on('click', '.open-tender-files', function () {
@@ -71,7 +274,7 @@ $(function () {
             toastr.error('获取资金划拨标段信息失败');
             return;
         }
-        const ftInfo = _.find(tenderList, { id: parseInt(ftid) });
+        const ftInfo = _.find(tenders, { id: parseInt(ftid) });
         if (!ftInfo) {
             toastr.error('获取资金划拨信息失败');
             return;
@@ -97,7 +300,7 @@ $(function () {
             }
         });
         const ftid = $('#tender-file input[name="ftid"]').val();
-        const ftInfo = _.find(tenderList, { id: parseInt(ftid) });
+        const ftInfo = _.find(tenders, { id: parseInt(ftid) });
         if (!ftInfo) {
             toastr.warning('不存在该资金划拨标段');
             $('#tender-file input[type="file"]').val('');
@@ -123,7 +326,7 @@ $(function () {
 
     $('body').on('click', '#tender-file .file-del', function () {
         const ftid = $('#tender-file input[name="ftid"]').val();
-        const ftInfo = _.find(tenderList, { id: parseInt(ftid) });
+        const ftInfo = _.find(tenders, { id: parseInt(ftid) });
         if (!ftInfo) {
             toastr.warning('不存在该资金划拨标段');
             return;
@@ -179,6 +382,146 @@ $(function () {
             autoFlashHeight();
         }
     });
+
+    // 添加标段树结构加载
+    const EmptyTenderListHtml = [
+        '<div class="jumbotron">',
+        '<h3 class="display-6">还没有标段数据</h3>',
+        '</div>'
+    ];
+    const tenderListTree = [];
+    let tenderListParentId = 0;
+    function initTenderListTree () {
+        const levelCategory = category.filter(function (c) {
+            return c.show_level && c.show_level > 0;
+        });
+        function findCategoryNode(cid, value, array) {
+            for (const a of array) {
+                if (a.cid === cid && a.vid === value) {
+                    return a;
+                }
+            }
+        }
+        function getCategoryNode(category, value, parent, i = null) {
+            const array = parent ?  parent.children : tenderListTree;
+            let cate = findCategoryNode(category.id, value, array);
+            if (!cate) {
+                const cateValue = findNode('id', value, category.value);
+                if (!cateValue) return null;
+                cate = {
+                    cid: category.id,
+                    vid: value,
+                    name: cateValue.value,
+                    children: [],
+                    level: i ? i : category.show_level,
+                    sort_id: ++tenderListParentId,
+                    sort: cateValue.sort,
+                };
+                array.push(cate);
+            }
+            return cate;
+        }
+        function loadTenderCategory (tender) {
+            let tenderCategory = null;
+            for (const [index, lc] of levelCategory.entries()) {
+                const tenderCate = findNode('cid', lc.id, tender.category);
+                if (tenderCate) {
+                    tenderCategory = getCategoryNode(lc, tenderCate.value, tenderCategory);
+                } else {
+                    if (index === 0 && tender.category) {
+                        for (const [i,c] of tender.category.entries()) {
+                            const cate = findNode('id', c.cid, category);
+                            if (cate) {
+                                tenderCategory = getCategoryNode(cate, c.value, tenderCategory, i+1);
+                            }
+                        }
+                    }
+                    return tenderCategory;
+                }
+            }
+            return tenderCategory;
+        }
+        tenderListTree.splice(0, tenderListTree.length);
+        for (const t of tenderList) {
+            t.valid = true;
+            delete t.level;
+            if (t.category && levelCategory.length > 0) {
+                const parent = loadTenderCategory(t);
+                if (parent) {
+                    t.level = parent.level + 1;
+                    parent.children.push(t);
+                } else {
+                    tenderListTree.push(t);
+                }
+            } else {
+                tenderListTree.push(t);
+            }
+        }
+        sortTenderTree(tenderListTree);
+    }
+    function recursiveGetTenderListNodeHtml (node, arr, pid) {
+        const html = [];
+        html.push('<tr pid="' + pid + '">');
+        html.push('<td style="width: 60px" class="text-center">');
+        if (!node.cid) {
+            html.push(`<input type="checkbox" class="tender-check" name="tender_id[]" value="${node.id}" ${_.findIndex(tenders, { tid: node.id }) !== -1 ? 'checked disabled' : ''} />`);
+        }
+        html.push('</td>');
+        // 名称
+        html.push('<td style="width: 65%" class="in-' + node.level + '">');
+        if (node.cid) {
+            html.push('<i class="fa fa-folder-o"></i> ', node.name);
+        } else {
+            html.push('<span class="text-muted mr-2">');
+            html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
+            html.push('</span>');
+            html.push(node.name);
+        }
+        html.push('</td>');
+        // 计量进度
+        html.push('<td style="width: 25%">');
+        if (!node.cid && node.cur_flow) {
+            if (node.progress) {
+                html.push(node.progress.title + ' (' + '<span class="' + node.progress.status_class + '">' + node.progress.status + '</span>' + ')');
+            } else {
+                html.push(node.cur_flow.title + ' (' + '<span class="' + node.cur_flow.status_class + '">' + node.cur_flow.status + '</span>' + ')');
+            }
+        }
+        html.push('</td>');
+        html.push('</tr>');
+        if (node.children) {
+            for (const c of node.children) {
+                html.push(recursiveGetTenderListNodeHtml(c, node.children, node.sort_id));
+            }
+        }
+        return html.join('');
+    }
+    // 根据TenderTree数据获取Html代码
+    function getTenderListTreeHtml () {
+        if (tenderListTree.length > 0) {
+            const html = [];
+            html.push('<table class="table table-hover table-bordered">');
+            html.push('<thead style="position: sticky;left:56px;top: 0px;">', '<tr>');
+            html.push('<th class="text-center" style="width: 60px;">', '选择', '</th>');
+            html.push('<th class="text-center" style="width: 65%">', '标段名称', '</th>');
+            html.push('<th class="text-center" style="width: 25%">', '计量进度', '</th>');
+            html.push('</tr>', '</thead>');
+            tenderListParentId = 0;
+            for (const t of tenderListTree) {
+                html.push(recursiveGetTenderListNodeHtml(t, tenderListTree, ''));
+            }
+            html.push('</table>');
+            return html.join('');
+        } else {
+            return EmptyTenderListHtml.join('');
+        }
+    }
+
+    $('#add-ftt').on('shown.bs.modal', function () {
+        tenderListOrder.reOrderTenders('', '#copyModalContent', false, tenderList);
+        initTenderListTree();
+        $('#add-tender-list').html(getTenderListTreeHtml());
+    });
 })
 /**
  * 校验文件大小、格式

+ 7 - 1
app/public/js/global.js

@@ -124,7 +124,13 @@ $(function(){
 
     $('.account-page-size').each(function () {
         if (getLocalCache('account-pageSize')) {
-            $(this).attr('href', $(this).attr('href') + '?pageSize=' + getLocalCache('account-pageSize'));
+            // 判断是否有?,是则变成&,否则变成?
+            const href = $(this).attr('href');
+            if (href.indexOf('?') !== -1) {
+                $(this).attr('href', href + '&pageSize=' + getLocalCache('account-pageSize'));
+            } else {
+                $(this).attr('href', href + '?pageSize=' + getLocalCache('account-pageSize'));
+            }
         }
     });
 

+ 3 - 3
app/public/js/shares/tender_list_order.js

@@ -28,14 +28,14 @@ const tenderListOrder = (function () {
         // }
         return pinyin.compareWord(x, y);
     }
-    function reOrderTenders (orderStr, htmlClass = '.c-body', loadHide = true) {
+    function reOrderTenders (orderStr, htmlClass = '.c-body', loadHide = true, ts = tenders) {
         if (orderStr) {
             orderSetting = orderStr;
             setLocalCache('zh-calc-tender-list-order', orderStr);
         }
         const orders = orderSetting.split('|');
         if (orders[0] === 'name') {
-            tenders.sort(function (a, b) {
+            ts.sort(function (a, b) {
                 // return orders[1] === 'up'
                 //     ? a[orders[0]].localeCompare(b[orders[0]], 'zh')
                 //     : -a[orders[0]].localeCompare(b[orders[0]], 'zh');
@@ -44,7 +44,7 @@ const tenderListOrder = (function () {
                     : CompareStr(b[orders[0]], a[orders[0]]);
             });
         } else if (orders[0] === 'create_time') {
-            tenders.sort(function (a, b){
+            ts.sort(function (a, b){
                 return orders[1] === 'up'
                     ? Date.parse(a[orders[0]]) - Date.parse(b[orders[0]])
                     : Date.parse(b[orders[0]]) - Date.parse(a[orders[0]]);

+ 9 - 4
app/public/js/sr_detail.js

@@ -189,11 +189,16 @@ $(document).ready(() => {
                 node.end_correct_tp = node.end_gather_tp;
             }
         }
-        node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
-        node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
+        // node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
+        // node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
         node.final_dgn_price = ZhCalc.round(ZhCalc.div(node.end_gather_tp, ZhCalc.add(node.deal_dgn_qty1, node.c_dgn_qty1)), tenderInfo.decimal.up);
-        node.end_final_1_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_1_tp), 100, 2);
-        node.end_correct_1_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_1_tp), 100, 2);
+        // node.end_final_1_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_1_tp), 100, 2);
+        // Task #5256要求修改
+        node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.tz2_tp), 100, 2);
+        node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.tz2_tp), 100, 2);
+        node.end_final_1_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.tz2_tp), 100, 2);
+        node.end_correct_1_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.tz2_tp), 100, 2);
+        // node.end_correct_1_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_1_tp), 100, 2);
     };
     const stageTree = createNewPathTree('stage', stageTreeSetting);
     // 初始化 计量单元 数据结构

+ 9 - 4
app/public/js/stage.js

@@ -344,11 +344,16 @@ $(document).ready(() => {
         } else if (tenderInfo.calc_type === 'up') {
             node.tz2_tp = ZhCalc.sum([node.total_price, node.tz_qc_tp, node.tz_qc_minus_tp]);
         }
-        node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
-        node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
+        // node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
+        // node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
         node.final_dgn_price = ZhCalc.round(ZhCalc.div(node.end_gather_tp, ZhCalc.add(node.deal_dgn_qty1, node.c_dgn_qty1)), tenderInfo.decimal.up);
-        node.end_final_1_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_1_tp), 100, 2);
-        node.end_correct_1_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_1_tp), 100, 2);
+        // node.end_final_1_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_1_tp), 100, 2);
+        // node.end_correct_1_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_1_tp), 100, 2);
+        // Task #5256要求修改
+        node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.tz2_tp), 100, 2);
+        node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.tz2_tp), 100, 2);
+        node.end_final_1_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.tz2_tp), 100, 2);
+        node.end_correct_1_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.tz2_tp), 100, 2);
     };
     const stageTree = createNewPathTree('stage', stageTreeSetting);
     // 初始化 计量单元 数据结构

+ 27 - 2
app/router.js

@@ -17,6 +17,7 @@ module.exports = app => {
     const changeApplyAuditCheck = app.middlewares.changeApplyAuditCheck();
     const changePlanAuditCheck = app.middlewares.changePlanAuditCheck();
     const uncheckTenderCheck = app.middlewares.uncheckTenderCheck();
+    const tenderPermissionCheck = app.middlewares.tenderPermissionCheck();
     // 期读取中间件
     const stageCheck = app.middlewares.stageCheck();
     const settleCheck = app.middlewares.settleCheck();
@@ -437,8 +438,10 @@ module.exports = app => {
     app.post('/sp/:id/financial/transfer/:trid/tender/:ttid/file/upload', sessionAuth, subProjectCheck, financialCheck, 'financialController.transferTenderUploadFile');
     app.post('/sp/:id/financial/transfer/:trid/tender/:ttid/file/delete', sessionAuth, subProjectCheck, financialCheck, 'financialController.transferTenderDeleteFile');
     app.get('/sp/:id/financial/transfer/:trid/tender/:ttid/file/:fid/download', sessionAuth, subProjectCheck, financialCheck, 'financialController.transferTenderDownloadFile');
-    app.get('/sp/:id/financial/pay', sessionAuth, subProjectCheck, financialCheck, 'financialController.pay');
-    app.post('/sp/:id/financial/pay/save', sessionAuth, subProjectCheck, financialCheck, 'financialController.paySave');
+    app.get('/sp/:id/financial/pay/stage', sessionAuth, subProjectCheck, financialCheck, 'financialController.payStage');
+    app.post('/sp/:id/financial/pay/stage/save', sessionAuth, subProjectCheck, financialCheck, 'financialController.payStageSave');
+    app.get('/sp/:id/financial/pay/stage/:fpsid', sessionAuth, subProjectCheck, financialCheck, 'financialController.pay');
+    app.post('/sp/:id/financial/pay/stage/:fpsid/save', sessionAuth, subProjectCheck, financialCheck, 'financialController.paySave');
     app.get('/sp/:id/financial/pay/:fpid/detail', sessionAuth, subProjectCheck, financialCheck, financialPayCheck, financialPayAuditCheck, 'financialController.payDetail');
     app.post('/sp/:id/financial/pay/:fpid/save', sessionAuth, subProjectCheck, financialCheck, financialPayCheck, 'financialController.payDetailSave');
     app.post('/sp/:id/financial/pay/:fpid/file/upload', sessionAuth, subProjectCheck, financialCheck, financialPayCheck, 'financialController.payUploadFile');
@@ -447,6 +450,8 @@ module.exports = app => {
     app.post('/sp/:id/financial/pay/:fpid/audit/start', sessionAuth, subProjectCheck, financialCheck, financialPayCheck, financialPayAuditCheck, 'financialController.startPayAudit');
     app.post('/sp/:id/financial/pay/:fpid/audit/check', sessionAuth, subProjectCheck, financialCheck, financialPayCheck, 'financialController.checkPayAudit');
     app.post('/sp/:id/financial/pay/:fpid/audit/check/again', sessionAuth, subProjectCheck, financialCheck, financialPayCheck, 'financialController.checkPayAgain');
+    app.get('/sp/:id/financial/pay/list', sessionAuth, subProjectCheck, financialCheck, 'financialController.payList');
+    app.post('/sp/:id/financial/pay/list/save', sessionAuth, subProjectCheck, financialCheck, 'financialController.payListSave');
     app.get('/sp/:id/financial/summary', sessionAuth, subProjectCheck, financialCheck, 'financialController.summary');
     app.post('/sp/:id/financial/summary/load', sessionAuth, subProjectCheck, financialCheck, 'financialController.summaryLoad');
 
@@ -455,6 +460,23 @@ module.exports = app => {
     // app.get('/sp/:id/drawing/design', sessionAuth, subProjectCheck, 'drawingController.design');
     // app.get('/sp/:id/drawing/as-built', sessionAuth, subProjectCheck, 'drawingController.built');
 
+    // 质量管理
+    app.get('/sp/:id/quality', sessionAuth, subProjectCheck, 'qualityController.tender');
+    app.post('/sp/:id/quality/member', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.member');
+    app.post('/sp/:id/quality/memberSave', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.memberSave');
+    app.get('/sp/:id/quality/tender/:tid/info', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.info');
+    app.get('/sp/:id/quality/tender/:tid/flaw', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.flaw');
+    app.get('/sp/:id/quality/tender/:tid/lab', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.lab');
+    app.post('/sp/:id/quality/tender/:tid/load', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.load');
+    app.post('/sp/:id/quality/tender/:tid/save/:type', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.save');
+    app.post('/sp/:id/quality/tender/:tid/file/upload', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.uploadFile');
+    app.post('/sp/:id/quality/tender/:tid/file/del', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.delFile');
+    app.post('/sp/:id/quality/tender/:tid/file/save', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.saveFile');
+    app.post('/sp/:id/quality/tender/:tid/push', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.pushStatus');
+    app.get('/sp/:id/quality/rule', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.rule');
+    app.post('/sp/:id/quality/rule/save', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.ruleSave');
+    app.post('/sp/:id/quality/tender/:tid/rule/save', sessionAuth, subProjectCheck, tenderCheck, projectManagerCheck, 'qualityController.ruleSave');
+
     // ------------------------- 项目内部相关 -----------------------------
 
 
@@ -1031,6 +1053,9 @@ module.exports = app => {
     app.get('/wap/tender/:id/change/plan/:cpid/information', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, changePlanCheck, changePlanAuditCheck, 'wapController.changePlan');
     app.get('/wap/tender/:id/material/:order', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, materialCheck, 'wapController.material');
     app.get('/wap/tender/:id/measure/material/:order', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, materialCheck, 'wapController.material');
+    app.get('/wap/inspection', 'wapController.inspection');
+    app.post('/wap/inspection/ask', 'wapController.inspectionAiAsk');
+    app.post('/wap/voice/token', 'wapController.voiceToken');
 
     // 微信
     app.get('/wx', 'wechatController.index');

+ 1 - 1
app/service/file.js

@@ -80,7 +80,7 @@ module.exports = app => {
 
             const fileDatas = await this.getAllDataByCondition({ where: { id: files } });
             const filing = await this.ctx.service.filing.getDataById(fileDatas[0].filing_id);
-            if (this.ctx.subProject.permission.file_permission.indexOf(this.ctx.service.subProjPermission.PermissionConst.file.delete.value) < 0) {
+            if (this.ctx.subProject.permission.file_permission.indexOf(this.ctx.service.subProjPermission.PermissionConst.file.editfile.value) < 0) {
                 for (const file of fileDatas) {
                     if (file.user_id !== this.ctx.session.sessionUser.accountId) throw '无权删除文件';
                 }

+ 66 - 3
app/service/financial_pay.js

@@ -28,6 +28,8 @@ module.exports = app => {
             const pay = await this.getDataById(id);
             const tender = await this.ctx.service.tender.getDataById(pay.tid);
             pay.tenderName = tender.name;
+            const payStage = pay.fpsid ? await this.ctx.service.financialPayStage.getOnePayStage(pay.fpsid) : null;
+            pay.payStage = payStage ? payStage : null;
             return pay;
         }
 
@@ -38,8 +40,12 @@ module.exports = app => {
          * @param {int} hadlimit - 分页
          * @return {object} list - 列表
          */
-        async getListByStatus(spid, status = 0, tid = null, used = null, hadlimit = 0, sortBy = '', orderBy = '') {
+        async getListByStatus(spid, fpsid = null, status = 0, tid = null, used = null, hadlimit = 0, sortBy = '', orderBy = '') {
             let addSql = '';
+            if (fpsid) {
+                fpsid = fpsid instanceof Array ? fpsid : [fpsid];
+                addSql += ' AND a.fpsid in (' + this.ctx.helper.getInArrStrSqlFilter(fpsid) + ')';
+            }
             if (tid !== null) {
                 if (tid.length === 0) {
                     return [];
@@ -110,8 +116,12 @@ module.exports = app => {
          * @param {int} status - 状态
          * @return {void}
          */
-        async getCountByStatus(spid, status = 0, tid = null, used = null) {
+        async getCountByStatus(spid, fpsid = null, status = 0, tid = null, used = null, company_id = null, user_company_id, qi = null) {
             let addSql = '';
+            if (fpsid) {
+                fpsid = fpsid instanceof Array ? fpsid : [fpsid];
+                addSql += ' AND a.fpsid in (' + this.ctx.helper.getInArrStrSqlFilter(fpsid) + ')';
+            }
             if (tid !== null) {
                 if (tid.length === 0) {
                     return 0;
@@ -162,7 +172,7 @@ module.exports = app => {
             }
         }
 
-        async addPay(spid, data) {
+        async addPay(spid, payStage, data) {
             if (!data.tid || !data.code || !data.used) {
                 throw '参数错误';
             }
@@ -175,6 +185,7 @@ module.exports = app => {
                 }
                 const insertData = {
                     spid,
+                    fpsid: payStage.id,
                     tid: data.tid,
                     code: data.code,
                     used: data.used,
@@ -198,6 +209,7 @@ module.exports = app => {
                         spid,
                         tid: data.tid,
                         fpid: result.insertId,
+                        fpsid: payStage.id,
                         aid: audit.audit_id,
                         order: audit.audit_order,
                         times: 1,
@@ -207,6 +219,7 @@ module.exports = app => {
                     });
                 }
                 if (insertAuditDatas.length > 0) await transaction.insert(this.ctx.service.financialPayAudit.tableName, insertAuditDatas);
+                if (payStage.can_del === 1) await transaction.update(this.ctx.service.financialPayStage.tableName, { id: payStage.id, can_del: 0 });
                 await transaction.commit();
                 return { id: result.insertId };
             } catch (e) {
@@ -232,6 +245,10 @@ module.exports = app => {
                 await transaction.delete(this.ctx.service.financialPayAtt.tableName, { fpid });
                 await transaction.delete(this.ctx.service.financialPayAudit.tableName, { fpid });
                 await transaction.delete(this.ctx.service.financialPayContract.tableName, { fpid });
+                if (node.fpsid) {
+                    const payStage = await this.ctx.service.financialPayStage.getDataById(node.fpsid);
+                    await this.ctx.service.financialPayStage.updatePayStageAndAfter(transaction, payStage.spid, payStage);
+                }
                 await transaction.commit();
             } catch (err) {
                 console.log(err);
@@ -303,6 +320,52 @@ module.exports = app => {
             }]);
             pay.finalAuditorIds = pay.userGroups[pay.userGroups.length - 1].map(x => { return x.aid; });
         }
+
+        async batchOldPays(spid, payStage, payIds) {
+            if (!payStage || !payIds || payIds.length === 0) {
+                throw '参数有误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                const updateAuditDatas = [];
+                const updateDatas = [];
+                const oldPays = await this.getAllDataByCondition({ where: { id: payIds } });
+                const oldPayAudits = await this.ctx.service.financialPayAudit.getAllDataByCondition({ where: { fpid: payIds } });
+                for (const node of oldPays) {
+                    if (node.fpsid) {
+                        throw '该支付已绑定单位期';
+                    }
+                    updateDatas.push({
+                        id: node.id,
+                        fpsid: payStage.id,
+                    });
+                    const oldPayAuditsByFpid = oldPayAudits.filter(x => { return x.fpid === node.id; });
+                    for (const audit of oldPayAuditsByFpid) {
+                        if (!audit.fpsid) {
+                            updateAuditDatas.push({
+                                id: audit.id,
+                                fpsid: payStage.id,
+                            });
+                        }
+                    }
+                }
+                if (updateDatas.length > 0) {
+                    await transaction.updateRows(this.tableName, updateDatas);
+                }
+                if (updateAuditDatas.length > 0) {
+                    await transaction.updateRows(this.ctx.service.financialPayAudit.tableName, updateAuditDatas);
+                }
+                // 更新本单位期金额和同单位的上期金额
+                await transaction.update(this.ctx.service.financialPayStage.tableName, { id: payStage.id, can_del: 0 });
+                await this.ctx.service.financialPayStage.updatePayStageAndAfter(transaction, spid, payStage);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                console.log(err);
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
     return FinancialPay;
 };

+ 8 - 7
app/service/financial_pay_contract.js

@@ -97,7 +97,7 @@ module.exports = app => {
             }
         }
 
-        async delContract(fpid, ids) {
+        async delContract(fpid, payStage, ids) {
             const transaction = await this.db.beginTransaction();
             try {
                 // 还要删除附件
@@ -107,7 +107,7 @@ module.exports = app => {
                 // 判断是否可删
                 await transaction.delete(this.tableName, { id: ids });
                 // 重新算资金支付总额
-                const tp = await this.calcCamountSum(fpid, transaction);
+                const tp = await this.calcCamountSum(fpid, payStage, transaction);
                 await transaction.commit();
                 return { tp, contractList: await this.getContractList(fpid) };
             } catch (err) {
@@ -116,11 +116,11 @@ module.exports = app => {
             }
         }
 
-        async updateContract(fpid, data) {
+        async updateContract(fpid, payStage, data) {
             const transaction = await this.db.beginTransaction();
             try {
                 await transaction.update(this.tableName, data);
-                const tp = await this.calcCamountSum(fpid, transaction);
+                const tp = await this.calcCamountSum(fpid, payStage, transaction);
                 await transaction.commit();
                 return { tp };
             } catch (err) {
@@ -134,7 +134,7 @@ module.exports = app => {
          * @param {Object} datas 修改内容
          * @return {void}
          */
-        async updateContracts(fpid, datas) {
+        async updateContracts(fpid, payStage, datas) {
             const transaction = await this.db.beginTransaction();
             try {
                 const updateArray = [];
@@ -144,7 +144,7 @@ module.exports = app => {
                     }
                 }
                 if (updateArray.length > 0) await transaction.updateRows(this.tableName, updateArray);
-                const tp = await this.calcCamountSum(fpid, transaction);
+                const tp = await this.calcCamountSum(fpid, payStage, transaction);
                 await transaction.commit();
                 return { tp, contractList: await this.getContractList(fpid) };
             } catch (err) {
@@ -153,7 +153,7 @@ module.exports = app => {
             }
         }
 
-        async calcCamountSum(fpid, transaction) {
+        async calcCamountSum(fpid, payStage, transaction) {
             // 防止小数位不精确,采用取值计算
             const sql = 'SELECT `small_expenses`, `pay_price` FROM ?? WHERE fpid = ?';
             const sqlParam = [this.tableName, fpid];
@@ -171,6 +171,7 @@ module.exports = app => {
                 small_expenses_tp,
             };
             await transaction.update(this.ctx.service.financialPay.tableName, updateData);
+            await this.ctx.service.financialPayStage.updatePayStageAndAfter(transaction, payStage.spid, payStage);
             return total_price;
         }
 

+ 262 - 0
app/service/financial_pay_stage.js

@@ -0,0 +1,262 @@
+'use strict';
+
+/**
+ * Created by EllisRan on 2020/3/3.
+ */
+
+const BaseService = require('../base/base_service');
+const auditConst = require('../const/audit').financial;
+const auditType = require('../const/audit').auditType;
+const shenpiConst = require('../const/shenpi');
+
+module.exports = app => {
+
+    class FinancialPayStage extends BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'financial_pay_stage';
+        }
+
+        async getOnePayStage(id) {
+            const sql = 'SELECT a.*, pa.name as username, c.name as company FROM ?? As a LEFT JOIN ?? As pa ON a.uid = pa.id LEFT JOIN ?? As c ON a.company_id = c.id WHERE a.id = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.constructionUnit.tableName, id];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        async getUserCompanyList(spid, user_company_id, unitList) {
+            let userCompanyList = [];
+            if (this.ctx.session.sessionUser.is_admin) {
+                userCompanyList = unitList;
+            } else {
+                let addSql = '';
+                let notAdminSql = '';
+                if (!this.ctx.session.sessionUser.is_admin) {
+                    notAdminSql += '(a.id in (SELECT fpsid FROM ' + this.ctx.service.financialPay.tableName + ' WHERE uid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY fpsid)' +
+                        ' OR a.id in (SELECT fpsid FROM ' + this.ctx.service.financialPayAudit.tableName + ' WHERE aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY fpsid))';
+                    addSql += ' AND (a.company_id = ' + user_company_id + ' OR ' + notAdminSql + ')';
+                }
+                const sql = 'SELECT a.company_id FROM ?? As a' +
+                    ' WHERE a.spid = ?' + addSql + ' GROUP BY company_id';
+                const sqlParam = [this.tableName, spid];
+                const list = await this.db.query(sql, sqlParam);
+                const companyIds = list.map(item => item.company_id);
+                userCompanyList = unitList.filter(unit => companyIds.includes(unit.id));
+                if (userCompanyList.length === 0 || userCompanyList.findIndex(u => u.id === user_company_id) === -1) {
+                    userCompanyList.push(unitList.find(u => u.id === user_company_id));
+                }
+            }
+            const allList = await this.getAllDataByCondition({ where: { spid } });
+            for (const u of userCompanyList) {
+                const count = allList.filter(item => item.company_id === u.id).length;
+                u.count = count;
+            }
+            return userCompanyList;
+        }
+
+        async getUserOrderList(spid, user_company_id) {
+            let addSql = '';
+            let notAdminSql = '';
+            if (!this.ctx.session.sessionUser.is_admin) {
+                notAdminSql += '(a.id in (SELECT fpsid FROM ' + this.ctx.service.financialPay.tableName + ' WHERE uid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY fpsid)' +
+                    ' OR a.id in (SELECT fpsid FROM ' + this.ctx.service.financialPayAudit.tableName + ' WHERE aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY fpsid))';
+                addSql += ' AND (a.company_id = ' + user_company_id + ' OR ' + notAdminSql + ')';
+            }
+            const sql = 'SELECT a.order FROM ?? As a' +
+                ' WHERE a.spid = ?' + addSql + ' GROUP BY a.order';
+            const sqlParam = [this.tableName, spid];
+            const list = await this.db.query(sql, sqlParam);
+            // 倒序
+            list.sort((a, b) => b.order - a.order);
+            const orderIds = list.map(item => item.order);
+            return orderIds;
+        }
+
+        /**
+         * 获取列表
+         * @param {int} spid - 项目id
+         * @param {int} status - 状态
+         * @param {int} hadlimit - 分页
+         * @return {object} list - 列表
+         */
+        async getListByStatus(spid, company_id = null, user_company_id, qi = null, hadlimit = 1) {
+            let addSql = '';
+            let notAdminSql = '';
+            if (!this.ctx.session.sessionUser.is_admin) {
+                notAdminSql += '(a.id in (SELECT fpsid FROM ' + this.ctx.service.financialPay.tableName + ' WHERE uid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY fpsid)' +
+                    ' OR a.id in (SELECT fpsid FROM ' + this.ctx.service.financialPayAudit.tableName + ' WHERE aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY fpsid))';
+            }
+            if (company_id !== null) {
+                addSql += ' AND (a.company_id = ' + company_id + (!this.ctx.session.sessionUser.is_admin && company_id !== user_company_id ? ' AND ' + notAdminSql : '') + ')';
+            } else if (company_id === null && !this.ctx.session.sessionUser.is_admin) {
+                addSql += ' AND (a.company_id = ' + user_company_id + ' OR ' + notAdminSql + ')';
+            }
+            // 判断qi是否存在且是否是字符串数字
+            if (qi !== null && !isNaN(qi)) {
+                addSql += ' AND a.order = ' + qi;
+            }
+            let sql = 'SELECT a.*, pa.name as username, c.name as company FROM ?? As a' +
+                ' LEFT JOIN ?? As pa ON a.uid = pa.id LEFT JOIN ?? As c ON a.company_id = c.id' +
+                ' WHERE a.spid = ?' + addSql + ' ORDER BY a.create_time DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.constructionUnit.tableName, spid];
+            if (hadlimit) {
+                const limit = this.ctx.pageSize ? this.ctx.pageSize : this.app.config.pageSize;
+                const offset = limit * (this.ctx.page - 1);
+                const limitString = offset >= 0 ? offset + ',' + limit : limit;
+                sql += ' LIMIT ' + limitString;
+            }
+            const list = await this.db.query(sql, sqlParam);
+            return list;
+        }
+
+        /**
+         * 获取支付个数
+         * @param {int} spid - 项目id
+         * @param {int} status - 状态
+         * @return {void}
+         */
+        async getCountByStatus(spid, company_id = null, user_company_id, qi = null) {
+            let addSql = '';
+            let notAdminSql = '';
+            if (!this.ctx.session.sessionUser.is_admin) {
+                notAdminSql += '(a.id in (SELECT fpsid FROM ' + this.ctx.service.financialPay.tableName + ' WHERE uid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY fpsid)' +
+                    ' OR a.id in (SELECT fpsid FROM ' + this.ctx.service.financialPayAudit.tableName + ' WHERE aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY fpsid))';
+            }
+            if (company_id !== null) {
+                addSql += ' AND (a.company_id = ' + company_id + (!this.ctx.session.sessionUser.is_admin && company_id !== user_company_id ? ' AND ' + notAdminSql : '') + ')';
+            } else if (company_id === null && !this.ctx.session.sessionUser.is_admin) {
+                addSql += ' AND (a.company_id = ' + user_company_id + ' OR ' + notAdminSql + ')';
+            }
+            // 判断qi是否存在且是否是字符串数字
+            if (qi !== null && !isNaN(qi)) {
+                addSql += ' AND a.order = ' + qi;
+            }
+            const sql = 'SELECT count(*) AS count FROM ?? As a' +
+                ' LEFT JOIN ?? As pa ON a.uid = pa.id LEFT JOIN ?? As c ON a.company_id = c.id' +
+                ' WHERE a.spid = ?' + addSql + ' ORDER BY a.create_time DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.constructionUnit.tableName, spid];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result ? result.count : 0;
+        }
+
+        async addPayStage(spid, data) {
+            if (!data.company_id || !data.order) {
+                throw '参数错误';
+            }
+            const times = new Date();
+            const info = await this.getDataByCondition({ spid, company_id: data.company_id, order: data.order });
+            if (info) {
+                throw '资金支付单位期已存在';
+            }
+            const allStageList = await this.getAllDataByCondition({ where: { spid, company_id: data.company_id } });
+            const transcation = await this.db.beginTransaction();
+            try {
+                await transcation.update(this.tableName, { can_del: 0 }, { where: { spid, company_id: data.company_id } });
+                const insertData = {
+                    spid,
+                    company_id: data.company_id,
+                    order: data.order,
+                    uid: this.ctx.session.sessionUser.accountId,
+                    pre_tp: this.ctx.helper.sum(this._.map(allStageList, 'total_price')) || 0,
+                    create_time: times,
+                };
+                const result = await transcation.insert(this.tableName, insertData);
+                await transcation.commit();
+                return await this.getOnePayStage(result.insertId);
+            } catch (err) {
+                await transcation.rollback();
+                throw err;
+            }
+        }
+
+        async delPayStage(fpsid) {
+            if (!fpsid) {
+                throw '参数有误';
+            }
+            const node = await this.getDataById(fpsid);
+            if (!node) {
+                throw '资金支付单位期不存在';
+            }
+            const payCount = await this.ctx.service.financialPay.count({ fpsid }) || 0;
+            if (payCount > 0) {
+                throw '该资金支付单位期下存在资金支付记录,不能删除';
+            }
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                await transaction.delete(this.tableName, { id: node.id });
+                // 判断是否能删除上一期
+                if (node.order > 1) {
+                    const lastPayStage = await this.getDataByCondition({ spid: node.spid, company_id: node.company_id, order: node.order - 1 });
+                    if (lastPayStage) {
+                        const stagePays = await transaction.select(this.ctx.service.financialPay.tableName, { where: { fpsid: lastPayStage.id } });
+                        if (stagePays.length === 0 && lastPayStage.can_del === 0) {
+                            await transaction.update(this.tableName, { id: lastPayStage.id, can_del: 1 });
+                        }
+                    }
+                }
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+        async savePayStage(fpsid, postData) {
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const updateData = {
+                    id: fpsid,
+                };
+                updateData[postData.name] = postData.val;
+                await transaction.update(this.tableName, updateData);
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+        async updatePayStageAndAfter(transaction, spid, payStage) {
+            let total_price = 0;
+            const stagePays = await transaction.select(this.ctx.service.financialPay.tableName, { where: { fpsid: payStage.id } });
+            for (const pay of stagePays) {
+                total_price = this.ctx.helper.add(total_price, pay.total_price);
+            }
+            await transaction.update(this.tableName, { id: payStage.id, total_price });
+            const allStagePays = await transaction.select(this.tableName, { where: { spid, company_id: payStage.company_id } });
+            const highestOrder = this._.max(this._.map(allStagePays, 'order')) || 0;
+            if (stagePays.length === 0 && highestOrder === payStage.order) {
+                // 如果当前支付单位期没有支付记录,并且是最高的order,则将其删除
+                await transaction.update(this.tableName, { id: payStage.id, can_del: 1 });
+            }
+            const afterStagePays = allStagePays.filter(x => { return x.order > payStage.order; });
+            if (afterStagePays && afterStagePays.length > 0) {
+                const pre_tps = [];
+                for (const stagePay of afterStagePays) {
+                    const beforeTotalPrice = this.ctx.helper.sum(this._.map(allStagePays.filter(x => { return x.order < stagePay.order; }), 'total_price')) || 0;
+                    pre_tps.push({
+                        id: stagePay.id,
+                        pre_tp: beforeTotalPrice,
+                    });
+                }
+                if (pre_tps.length > 0) {
+                    await transaction.updateRows(this.tableName, pre_tps);
+                }
+            }
+        }
+    }
+    return FinancialPayStage;
+};

+ 45 - 0
app/service/financial_pay_tender.js

@@ -32,6 +32,51 @@ module.exports = app => {
             await this.db.update(this.tableName, data);
             return await this.getDataById(data.id);
         }
+
+        async copyBank2otherTender(spid, tenders, copy_tender) {
+            const insertDatas = [];
+            const updateDatas = [];
+            for (const tender of tenders) {
+                if (tender.tid === copy_tender.tid) {
+                    continue; // 跳过复制的标
+                }
+                if (tender.id === 0) {
+                    delete tender.id;
+                    insertDatas.push({
+                        tid: tender.tid,
+                        spid,
+                        name: copy_tender.name,
+                        bank: copy_tender.bank,
+                        bank_account: copy_tender.bank_account,
+                        contact: copy_tender.contact,
+                        phone: copy_tender.phone,
+                    });
+                } else {
+                    updateDatas.push({
+                        id: tender.id,
+                        name: copy_tender.name,
+                        bank: copy_tender.bank,
+                        bank_account: copy_tender.bank_account,
+                        contact: copy_tender.contact,
+                        phone: copy_tender.phone,
+                    });
+                }
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (insertDatas.length > 0) {
+                    await transaction.insert(this.tableName, insertDatas);
+                }
+                if (updateDatas.length > 0) {
+                    await transaction.updateRows(this.tableName, updateDatas);
+                }
+                await transaction.commit();
+            } catch (e) {
+                await transaction.rollback();
+                throw e;
+            }
+            return await this.getAllDataByCondition({ where: { spid } });
+        }
     }
     return FinancialPayTender;
 };

+ 11 - 0
app/service/financial_transfer.js

@@ -42,8 +42,14 @@ module.exports = app => {
             if (node) {
                 throw '资金划拨年月已存在,请勿重复';
             }
+            let pre_hb_tp = 0;
+            const ftList = await this.getAllDataByCondition({ where: { spid } });
+            if (ftList.length > 0) {
+                pre_hb_tp = this.ctx.helper.sum(this._.map(ftList, 'total_price'));
+            }
             const insertData = {
                 spid,
+                pre_hb_tp,
                 t_time: date,
                 create_time: new Date(),
                 remark,
@@ -71,7 +77,12 @@ module.exports = app => {
                 const attList = await this.ctx.service.financialTransferAtt.getAllDataByCondition({ where: { trid } });
                 const tenderAttList = await this.ctx.service.financialTransferTenderAtt.getAllDataByCondition({ where: { trid } });
                 await this.ctx.helper.delFiles(this._.concat(attList, tenderAttList));
+                // 重算截止上次划拨金额
+                const fttList = await this.ctx.service.financialTransferTender.getAllDataByCondition({ where: { trid } });
                 await transaction.delete(this.ctx.service.financialTransferTender.tableName, { trid });
+                for (const ftt of fttList) {
+                    await this.ctx.service.financialTransferTender.calcPreHbTp(transaction, node, ftt);
+                }
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();

+ 151 - 20
app/service/financial_transfer_tender.js

@@ -22,13 +22,13 @@ module.exports = app => {
         }
 
         async getList(trid, pageLimit = 0) {
-            let sql = 'SELECT ftt.*, t.`name` as name FROM ?? as ftt LEFT JOIN ?? as t ON ftt.`tid` = t.`id` WHERE ftt.`trid` = ?';
-            if (pageLimit) {
-                const limit = this.ctx.pageSize ? this.ctx.pageSize : this.app.config.pageSize;
-                const offset = limit * (this.ctx.page - 1);
-                const limitString = offset >= 0 ? offset + ',' + limit : limit;
-                sql += ' LIMIT ' + limitString;
-            }
+            const sql = 'SELECT ftt.*, t.`name` as name FROM ?? as ftt LEFT JOIN ?? as t ON ftt.`tid` = t.`id` WHERE ftt.`trid` = ?';
+            // if (pageLimit) {
+            //     const limit = this.ctx.pageSize ? this.ctx.pageSize : this.app.config.pageSize;
+            //     const offset = limit * (this.ctx.page - 1);
+            //     const limitString = offset >= 0 ? offset + ',' + limit : limit;
+            //     sql += ' LIMIT ' + limitString;
+            // }
             const sqlParams = [this.tableName, this.ctx.service.tender.tableName, trid];
             const list = await this.db.query(sql, sqlParams);
             for (const l of list) {
@@ -41,27 +41,87 @@ module.exports = app => {
             const transaction = await this.db.beginTransaction();
             try {
                 const insertDatas = [];
+                const needCalc = [];
+                const updateDatas = [];
+                const ftList = await this.ctx.service.financialTransfer.getAllDataByCondition({ where: { spid: transferInfo.spid } });
+                const beforeFt = this._.filter(ftList, function(item) {
+                    return item.id < transferInfo.id;
+                });
+                const afterFt = this._.filter(ftList, function(item) {
+                    return item.id > transferInfo.id;
+                });
                 for (const t of tenders) {
-                    const tender = await this.ctx.service.tender.getDataById(t.tid);
-                    const stages = await this.ctx.service.stage.getAllDataByCondition({ where: { tid: t.tid, order: t.sorder } });
+                    const tender = await this.ctx.service.tender.getDataById(t);
+                    if (!tender) {
+                        throw '标段不存在';
+                    }
+                    const ftt = await this.getDataByCondition({ spid: transferInfo.spid, trid: transferInfo.id, tid: t });
+                    if (ftt) {
+                        throw '资金划拨标段已存在';
+                    }
+                    await this.ctx.service.tenderCache.loadTenderCache(tender, this.ctx.session.sessionUser.accountId);
+                    if (tender.stage_tp) {
+                        tender.end_sf_tp = this.ctx.helper.add(tender.stage_tp.pre_sf_tp, tender.stage_tp.sf_tp);
+                    } else if (tender.lastStage) {
+                        tender.end_sf_tp = this.ctx.helper.add(tender.lastStage.pre_sf_tp, tender.lastStage.sf_tp);
+                    }
+                    // 找出已添加的划拨标段,得出pre_hb_tp
+                    let pre_hb_tp = 0;
+                    if (beforeFt.length > 0) {
+                        const beforeFttList = await this.getAllDataByCondition({ where: { spid: transferInfo.spid, trid: this._.map(beforeFt, 'id'), tid: t } });
+                        if (beforeFttList && beforeFttList.length > 0) {
+                            pre_hb_tp = this._.sumBy(beforeFttList, 'hb_tp');
+                        }
+                    }
+                    // const stages = await this.ctx.service.stage.getAllDataByCondition({ where: { tid: t.tid, order: t.sorder } });
                     const insertData = {
                         spid: transferInfo.spid,
                         trid: transferInfo.id,
-                        tid: t.tid,
-                        sorder: t.sorder.join(','),
+                        tid: t,
                         uid: this.ctx.session.sessionUser.accountId,
-                        total_price: tender.total_price,
-                        contract_tp: this._.sumBy(stages, 'contract_tp'),
-                        qc_tp: this._.sumBy(stages, 'qc_tp'),
-                        pc_tp: this._.sumBy(stages, 'pc_tp'),
-                        yf_tp: this._.sumBy(stages, 'yf_tp'),
-                        sf_tp: this._.sumBy(stages, 'sf_tp'),
-                        hb_tp: this._.sumBy(stages, 'sf_tp'),
+                        hb_tp: tender.end_sf_tp,
+                        pre_hb_tp,
                     };
+                    if (afterFt.length > 0) {
+                        const afterFttList = await this.getAllDataByCondition({ where: { spid: transferInfo.spid, trid: this._.map(afterFt, 'id'), tid: t } });
+                        if (afterFttList && afterFttList.length > 0) {
+                            for (const aftt of afterFttList) {
+                                aftt.pre_hb_tp = this.ctx.helper.add(aftt.pre_hb_tp, insertData.hb_tp);
+                                updateDatas.push({ id: aftt.id, pre_hb_tp: aftt.pre_hb_tp });
+                                needCalc.push(aftt.trid);
+                            }
+                        }
+                    }
                     insertDatas.push(insertData);
                 }
                 await transaction.insert(this.tableName, insertDatas);
+                if (updateDatas.length > 0) {
+                    await transaction.updateRows(this.tableName, updateDatas);
+                }
+                // 计算划拨金额
                 await this.calcHbTp(transferInfo.id, transaction);
+                if (needCalc.length > 0) {
+                    const newFtList = await transaction.select(this.ctx.service.financialTransfer.tableName, { where: { spid: transferInfo.spid } });
+                    // 更新划拨金额
+                    const updatePreHbTp = [];
+                    for (const tr of needCalc) {
+                        // await this.calcHbTp(tr, transaction);
+                        const thisFt = this._.find(newFtList, { id: tr });
+                        let pre_hb_tp = 0;
+                        const beforeFtList = this._.filter(newFtList, function(item) {
+                            return item.id < tr;
+                        });
+                        if (beforeFtList && beforeFtList.length > 0) {
+                            pre_hb_tp = this.ctx.helper.sum(this._.map(beforeFtList, 'total_price'));
+                        }
+                        if (pre_hb_tp !== thisFt.pre_hb_tp) {
+                            updatePreHbTp.push({ id: tr, pre_hb_tp });
+                        }
+                    }
+                    if (updatePreHbTp.length > 0) {
+                        await transaction.updateRows(this.ctx.service.financialTransfer.tableName, updatePreHbTp);
+                    }
+                }
                 await transaction.commit();
                 return true;
             } catch (e) {
@@ -71,7 +131,7 @@ module.exports = app => {
             }
         }
 
-        async delTenders(ftid) {
+        async delTenders(transferInfo, ftid) {
             if (!ftid) {
                 throw '参数有误';
             }
@@ -86,6 +146,7 @@ module.exports = app => {
                 const attList = await this.ctx.service.financialTransferTenderAtt.getAllDataByCondition({ where: { ftid } });
                 await this.ctx.helper.delFiles(attList);
                 await this.calcHbTp(node.trid, transaction);
+                await this.calcPreHbTp(transaction, transferInfo, node);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -94,7 +155,7 @@ module.exports = app => {
             return true;
         }
 
-        async updateHbTp(ftid, hb_tp) {
+        async updateHbTp(transferInfo, ftid, hb_tp) {
             if (!ftid) {
                 throw '参数有误';
             }
@@ -110,6 +171,7 @@ module.exports = app => {
             try {
                 await transaction.update(this.tableName, { id: node.id, hb_tp });
                 await this.calcHbTp(node.trid, transaction);
+                await this.calcPreHbTp(transaction, transferInfo, node, hb_tp);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -118,6 +180,75 @@ module.exports = app => {
             return true;
         }
 
+        async updateTender(transferInfo, postData) {
+            const keys = ['source'];
+            if (!this._.includes(keys, postData.key)) {
+                throw '参数有误';
+            }
+            const node = await this.getDataById(postData.node);
+            if (!node) {
+                throw '资金划拨标段不存在';
+            }
+            if (node.is_lock) {
+                throw '资金划拨标段已锁定,无法修改';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                const updateData = {
+                    id: node.id,
+                };
+                updateData[postData.key] = postData.value;
+                await transaction.update(this.tableName, updateData);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        async calcPreHbTp(transaction, transferInfo, ftt, newHbTp = 0) {
+            const ftList = await transaction.select(this.ctx.service.financialTransfer.tableName, { where: { spid: transferInfo.spid } });
+            const afterFt = this._.filter(ftList, function(item) {
+                return item.id > transferInfo.id;
+            });
+            if (afterFt.length === 0) return;
+            const needCalc = [];
+            const updateDatas = [];
+            const afterFttList = await this.getAllDataByCondition({ where: { spid: transferInfo.spid, trid: this._.map(afterFt, 'id'), tid: ftt.tid } });
+            if (afterFttList && afterFttList.length > 0) {
+                for (const aftt of afterFttList) {
+                    aftt.pre_hb_tp = this.ctx.helper.add(this.ctx.helper.sub(aftt.pre_hb_tp, ftt.hb_tp), newHbTp);
+                    updateDatas.push({ id: aftt.id, pre_hb_tp: aftt.pre_hb_tp });
+                    needCalc.push(aftt.trid);
+                }
+            }
+            if (updateDatas.length > 0) {
+                await transaction.updateRows(this.tableName, updateDatas);
+            }
+            if (needCalc.length > 0) {
+                // 更新划拨金额
+                const updatePreHbTp = [];
+                for (const tr of needCalc) {
+                    // await this.calcHbTp(tr, transaction);
+                    const thisFt = this._.find(ftList, { id: tr });
+                    let pre_hb_tp = 0;
+                    const beforeFtList = this._.filter(ftList, function(item) {
+                        return item.id < tr;
+                    });
+                    if (beforeFtList && beforeFtList.length > 0) {
+                        pre_hb_tp = this.ctx.helper.sum(this._.map(beforeFtList, 'total_price'));
+                    }
+                    if (pre_hb_tp !== thisFt.pre_hb_tp) {
+                        updatePreHbTp.push({ id: tr, pre_hb_tp });
+                    }
+                }
+                if (updatePreHbTp.length > 0) {
+                    await transaction.updateRows(this.ctx.service.financialTransfer.tableName, updatePreHbTp);
+                }
+            }
+        }
+
         async calcHbTp(trid, transaction = null) {
             const sql = 'SELECT SUM(hb_tp) as hb_tp FROM ?? WHERE `trid` = ?';
             const sqlParams = [this.tableName, trid];

+ 1 - 1
app/service/stage_bills_final.js

@@ -119,7 +119,7 @@ module.exports = app => {
                     c.negative_qc_tp = this.ctx.helper.add(c.negative_qc_tp, cp.negative_qc_pc_tp);
                     curPc.splice(curPc.indexOf(cp), 1);
                 }
-                if (!c.unit_price) {
+                if (!c.unit_price || (!p.used && c.used)) {
                     const bills = await this.ctx.service.ledger.getDataById(c.lid);
                     c.unit_price = bills ? bills.unit_price || 0 : 0;
                 }

+ 1 - 1
app/service/sub_proj_permission.js

@@ -33,7 +33,7 @@ module.exports = app => {
                 file: {
                     view: { title: '查看', value: 1 },
                     upload: { title: '上传/引用', value: 2 },
-                    delete: { title: '删除文件', value: 4 },
+                    editfile: { title: '编辑文件', value: 4 },
                     filing: { title: '文件类别编辑', value: 3 },
                 },
                 manage: {

+ 29 - 26
app/view/file/file.ejs

@@ -13,8 +13,8 @@
     <div class="content-wrap row pr-46">
         <div class="row w-100 sub-content">
             <div class="c-body" id="left-view" style="width: 100%">
-                <div class="sjs-height-0 row" style="width: 100%">
-                    <div class="col-3 border-right pr-0">
+                <div class="sjs-height-0 row w-100" style="margin-left: 0px !important;">
+                    <div class="border-right" id="file-left-view" style="width: 25%;">
                         <div class="d-flex flex-row">
                             <div class="btn-group">
                                 <button type="button" class="btn btn-sm  text-primary dropdown-toggle" data-toggle="dropdown" id="zhankai" aria-expanded="false">显示层级</button>
@@ -33,36 +33,39 @@
                         </div>
                         <ul id="filing" class="ztree" style="overflow: auto"></ul>
                     </div>
-                    <div class="col-9 pr-0" id="file-view" style="display: none">
-                        <div class="d-flex flex-row">
-                            <% if (canUpload) { %>
-                            <div class="py-2 pr-2"><a href="#add-file" data-toggle="modal" data-target="#add-file">上传文件</a></div>
-                            <div class="py-2 pr-2"><a href="#add-big-file" data-toggle="modal" data-target="#add-big-file">大文件上传</a></div>
-                            <div class="p-2" id="rela-file-btn"><a href="#rela-file" data-toggle="modal" data-target="#rela-file">引用文件</a></div>
-                            <div class="p-2"><a href="javascript: void(0)" id="batch-del-file-btn">批量删除</a></div>
-                            <% } %>
-                            <div class="p-2"><a href="javascript: void(0)" id="batch-download">批量下载</a></div>
-                            <div class="p-2">
+                    <div class="" id="file-right-view" style="width: 74%">
+                        <div class="resize-x" id="file-right-spr" r-Type="width" div1="#file-left-view" div2="#file-right-view" title="调整大小" a-type="percent" store-id="file-detail" store-version="1.0.0" min="20"></div>
+                        <div class="ml-2" id="file-view" style="display: none">
+                            <div class="d-flex flex-row">
+                                <% if (canUpload) { %>
+                                <div class="py-2 pr-2"><a href="#add-file" data-toggle="modal" data-target="#add-file">上传文件</a></div>
+                                <div class="py-2 pr-2"><a href="#add-big-file" data-toggle="modal" data-target="#add-big-file">大文件上传</a></div>
+                                <div class="p-2" id="rela-file-btn"><a href="#rela-file" data-toggle="modal" data-target="#rela-file">引用文件</a></div>
+                                <div class="p-2"><a href="javascript: void(0)" id="batch-del-file-btn">批量删除</a></div>
+                                <% } %>
+                                <div class="p-2"><a href="javascript: void(0)" id="batch-download">批量下载</a></div>
+                                <div class="p-2">
                             <span id="showPage">
                                 <a href="javascript:void(0);" class="page-select ml-3" content="pre"><i class="fa fa-chevron-left"></i></a>
                                 <span id="curPage">1</span>/<span id="curTotalPage">10</span>
                                 <a href="javascript:void(0);" class="page-select mr-3" content="next"><i class="fa fa-chevron-right"></i></a>
                             </span>
+                                </div>
                             </div>
+                            <table class="table table-hover table-bordered">
+                                <thead>
+                                <tr class="text-center">
+                                    <th width="60px">选择</th>
+                                    <th>文件名称 <span name="file-sort" field="filename" tag="filename|desc"><i class="fa fa-sort" aria-hidden="true"></i></span></th>
+                                    <th width="10%">上传人</th>
+                                    <th width="20%">上传时间 <span name="file-sort" field="create_time" tag="create_time|asc"><i class="fa fa-sort-amount-desc" aria-hidden="true"></i></span></th>
+                                    <th width="10%">文件类型</th>
+                                </tr>
+                                </thead>
+                                <tbody id="file-list">
+                                </tbody>
+                            </table>
                         </div>
-                        <table class="table table-hover table-bordered">
-                            <thead>
-                            <tr class="text-center">
-                                <th width="60px">选择</th>
-                                <th>文件名称 <span name="file-sort" field="filename" tag="filename|desc"><i class="fa fa-sort" aria-hidden="true"></i></span></th>
-                                <th width="10%">上传人</th>
-                                <th width="20%">上传时间 <span name="file-sort" field="create_time" tag="create_time|asc"><i class="fa fa-sort-amount-desc" aria-hidden="true"></i></span></th>
-                                <th width="10%">文件类型</th>
-                            </tr>
-                            </thead>
-                            <tbody id="file-list">
-                            </tbody>
-                        </table>
                     </div>
                 </div>
             </div>
@@ -112,6 +115,6 @@
     const filing = JSON.parse(unescape('<%- escape(JSON.stringify(filing)) %>'));
     const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
     const whiteList = JSON.parse('<%- JSON.stringify(ctx.app.config.multipart.whitelist) %>');
-    const canDelete = <%- canDelete %>;
+    const canEdit = <%- canEdit %>;
     const fileReferenceList = JSON.parse('<%- JSON.stringify(fileReferenceList) %>');
 </script>

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

@@ -221,4 +221,5 @@
             </div>
         </div>
     </div>
-</div>
+</div>
+<% include ../shares/delete_hint_modal.ejs %>

+ 14 - 9
app/view/financial/pay.ejs

@@ -4,8 +4,8 @@
         <div class="title-main  d-flex">
             <% include ./sub_mini_menu.ejs %>
             <div class="col-10 pl-0">
-                <div class="d-inline-block"><span class="mr-3">支付列表</span></div>
-                <div class="d-inline-block col-sm-3">
+                <div class="d-inline-block"><a href="<%- preUrl %>" class="account-page-size"><i class="fa fa-chevron-left "></i> <span>返回</span></a><span class="text-muted mx-2">|</span><span>第<%- payStage.order %>期 <%- payStage.company %></span><span class="text-muted mx-2">|</span></div>
+                <div class="d-inline-block col-sm-3 pl-0">
                     <div class="input-group input-group-sm pr-1">
                         <select class="form-control form-control-sm col-auto" id="tid_select">
                             <option value="0">全部</option>
@@ -44,12 +44,11 @@
                         </div>
                     </div>
                 </div>
+                <% if (notStagePays.length > 0 && (ctx.session.sessionUser.is_admin || fptReportTids.length > 0)) { %>
                 <div class="d-inline-block">
-                    <a href="#payaccount" data-toggle="modal" data-target="#payaccount" class="btn btn-sm btn-primary mr-2" id="show-pay-account" style="display: none">付款账号</a>
-                    <% if (ctx.session.sessionUser.is_admin) { %>
-                    <a href="#liucheng" data-toggle="modal" data-target="#liucheng" class="btn btn-sm btn-primary mr-2">审批流程</a>
-                    <% } %>
+                    <a href="#contract-old-pay" data-toggle="modal" data-target="#contract-old-pay" class="btn btn-sm btn-primary mr-2">关联旧数据</a>
                 </div>
+                <% } %>
             </div>
             <div class="d-inline-block ml-auto">
                 <% if (ctx.session.sessionUser.is_admin || fptReportTids.length > 0) { %>
@@ -60,8 +59,7 @@
         </div>
     </div>
     <div class="content-wrap">
-        <div class="c-body">
-            <div class="sjs-height-0">
+        <div class="sjs-height-0" style="background-color: #fff">
                 <table class="table table-bordered text-center">
                     <thead>
                     <tr>
@@ -122,7 +120,6 @@
                 <!--翻页-->
                 <% include ../layout/page.ejs %>
             </div>
-        </div>
     </div>
 </div>
 <link href="/public/css/bootstrap/select2.min.css" rel="stylesheet" />
@@ -130,9 +127,17 @@
 <script src="/public/js/bootstrap/select2.min.js"></script>
 <script>
     const user_id = <%- ctx.session.sessionUser.accountId %>;
+    const fpsid = <%- payStage.id %>;
     const is_admin = <%- ctx.session.sessionUser.is_admin %>;
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
     const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenders)) %>'));
+    console.log(tenders);
     const fptReportTids = JSON.parse(unescape('<%- escape(JSON.stringify(fptReportTids)) %>'));
     const auditConst = JSON.parse(unescape('<%- escape(JSON.stringify(auditConst)) %>'));
     const auditType = JSON.parse(unescape('<%- escape(JSON.stringify(auditType)) %>'));
+    const selfCategoryLevel = '';
+    const pid = '<%- ctx.session.sessionProject.id %>';
+    const subProid = '<%- ctx.subProject.id %>';
+    const from = '<%- ctx.query.from || '' %>';
+    const uphlname = 'user_' + user_id + '_pro_' + pid + '_sub_' + subProid + '_pay_category_hide_list';
 </script>

+ 3 - 0
app/view/financial/pay_detail.ejs

@@ -4,6 +4,9 @@
         <div class="title-main  d-flex">
             <% include ./sub_mini_menu.ejs %>
             <div class="col-10 pl-0">
+                <% if (financialPay.payStage || returnUrl.indexOf('/pay/list') !== -1) { %>
+                <div class="d-inline-block"><a href="<%- returnUrl %>" class="account-page-size"><i class="fa fa-chevron-left "></i> <span>返回</span></a><span class="text-muted mx-2">|</span></div>
+                <% } %>
 <!--                <div class="d-inline-block mr-3">-->
 <!--                    <div class="btn-group btn-group-toggle group-tab" data-toggle="buttons">-->
 <!--                        <label class="btn btn-sm btn-light active">-->

+ 197 - 0
app/view/financial/pay_list.ejs

@@ -0,0 +1,197 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main  d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div class="col-11 pl-0">
+                <div class="btn-group group-tab">
+                    <a class="btn btn-sm btn-light account-page-size" href="/sp/<%- ctx.subProject.id %>/financial/pay/stage">支付列表</a>
+                    <a class="btn btn-sm btn-light active" href="javascript:void(0);">汇总列表</a>
+                    <a class="btn btn-sm btn-light" href="/financial/pay">标段统计</a>
+                </div>
+                <div class="d-inline-block col-sm-2">
+                    <div class="input-group input-group-sm pr-1">
+                        <select class="form-control form-control-sm col-auto" id="company_select">
+                            <option value="0">筛选单位</option>
+                            <% for (const c of userCompanyList) { %>
+                                <option value="<%- c.id %>" <% if (c.name === company) { %>selected<% } %>><%- c.name %></option>
+                            <% } %>
+                        </select>
+                    </div>
+                </div>
+                <div class="d-inline-block col-sm-1 pl-0">
+                    <div class="input-group input-group-sm pr-1">
+                        <select class="form-control form-control-sm col-auto" id="order_select">
+                            <option selected="0">筛选期数</option>
+                            <% for (const uo of userOrderList) { %>
+                                <option value="<%- uo %>" <% if (uo === qi) { %>selected<% } %>>第<%- uo %>期</option>
+                            <% } %>>
+                        </select>
+                    </div>
+                </div>
+                <div class="d-inline-block col-sm-2 pl-0">
+                    <div class="input-group input-group-sm pr-1">
+                        <select class="form-control form-control-sm col-auto" id="tid_select">
+                            <option value="0">筛选标段</option>
+                            <% for (const t of tenders) { %>
+                            <option value="<%- t.id %>" <% if (t.id === tid) { %>selected<% } %> ><%- t.name %></option>
+                            <% } %>
+                        </select>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <div class="input-group input-group-sm pr-1">
+                        <div class="btn-group">
+                            <button type="button" class="btn btn-sm btn-light text-primary dropdown-toggle" data-toggle="dropdown" id="used_selected" data-value="<%- used %>">资金用途:<%- used ? used : '全部' %></button>
+                            <div class="dropdown-menu" aria-labelledby="used_selected" id="used_select">
+                                <% if (used !== null) { %><a class="dropdown-item to-log-link" data-val="" href="javascript:void(0);">全部</a><% } %>
+                                <% for (const u of usedList) { %>
+                                <% if (used !== u) { %>
+                                <a class="dropdown-item to-log-link" href="javascript:void(0)" data-val="<%- u %>"><%- u %></a>
+                                <% } %>
+                                <% } %>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <div class="input-group input-group-sm pr-1">
+                        <div class="btn-group">
+                            <button type="button" class="btn btn-sm btn-light text-primary dropdown-toggle" data-toggle="dropdown" id="status_selected" data-value="<%- status %>">审批状态:<% if (status !== 0) { %><%- filter.statusString[status] %>(<%- filter.count[status] %>)<% } else { %>全部<% } %></button>
+                            <div class="dropdown-menu" aria-labelledby="status_selected" id="status_select">
+                                <% if (status !== 0) { %><a class="dropdown-item to-log-link" data-val="0" href="javascript:void(0);">全部</a><% } %>
+                                <% for (const fs in filter.status) { %>
+                                    <% const f = filter.status[fs]; %>
+                                    <% if (f !== status) { %><a class="dropdown-item to-log-link" data-val="<%- f %>" href="javascript:void(0);"><%- filter.statusString[f] %>(<%- filter.count[f] %>)</a><% } %>
+                                <% } %>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="d-inline-block ml-auto">
+<!--                <a href="#batch-sp" data-toggle="modal" data-target="#batch-sp" class="btn btn-success btn-sm pull-right mr-2">批量审批</a>-->
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="sjs-height-0" style="background-color: #fff">
+                <table class="table table-bordered text-center">
+                    <thead>
+                    <tr>
+                        <th width="250px">申请单位</th>
+                        <th width="100px">关联期数</th>
+                        <th style="min-width: 150px;">标段名称</th>
+                        <th width="200px">支付编号</th>
+                        <th width="150px">申请时间</th>
+                        <th width="100px">申请人</th>
+                        <th width="200px">资金用途</th>
+                        <th width="300px">收款单位</th>
+                        <th width="150px">支付金额</th>
+                        <th width="200px">审批进度</th>
+                        <th width="150px">操作</th>
+                    </tr>
+                    </thead>
+                    <tbody id="pay-list">
+                    <% for (const pay of payList) { %>
+                        <tr class="text-center" data-tid="<%- pay.tid %>">
+                            <td class="text-left"><%- pay.stage ? pay.stage.company : '' %></td>
+                            <td class=""><% if (pay.stage) { %><a href="/sp/<%- ctx.subProject.id %>/financial/pay/stage/<%- pay.stage.id %>?from=list" class="account-page-size">第<%- pay.stage.order %>期</a><% } %></td>
+                            <td class="text-left"><%- pay.tenderName %></td>
+                            <td class=""><a href="/sp/<%- ctx.subProject.id %>/financial/pay/<%- pay.id %>/detail?from=list"><%- pay.code %></a></td>
+                            <td class=""><%- moment(pay.create_time).format('YYYY-MM-DD') %></td>
+                            <td class=""><%- pay.username %></td>
+                            <td class=""><%- pay.used %></td>
+                            <td class=""><%- pay.entities %></td>
+                            <td class="text-right"><%- pay.total_price %></td>
+                            <td class="text-left <%- auditConst.auditProgressClass[pay.status] %>">
+                                <% if (pay.status === auditConst.status.checked && pay.final_auditor_str) { %>
+                                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" c-id="<%- pay.id %>"><%- pay.final_auditor_str %></a>
+                                <% } else if (pay.curAuditors.length > 0) { %>
+                                    <% if (pay.curAuditors[0].audit_type === auditType.key.common) { %>
+                                        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" c-id="<%- pay.id %>"><%- pay.curAuditors[0].name %><%if (pay.curAuditors[0].role !== '' && pay.curAuditors[0].role !== null) { %>-<%- pay.curAuditors[0].role %><% } %></a>
+                                    <% } else { %>
+                                        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" c-id="<%- pay.id %>"><%- ctx.helper.transFormToChinese(pay.curAuditors[0].audit_order) + '审' %></a>
+                                    <% } %>
+                                <% } %>
+                                <%- auditConst.auditProgress[pay.status] %>
+                            </td>
+                            <td>
+                                <% if (pay.status === auditConst.status.uncheck && pay.uid === ctx.session.sessionUser.accountId) { %>
+                                    <a href="/sp/<%- ctx.subProject.id %>/financial/pay/<%- pay.id %>/detail?from=list" class="btn <%- auditConst.statusButtonClass[pay.status] %> btn-sm"><%- auditConst.statusButton[pay.status] %></a>
+                                <% } else if (pay.status === auditConst.status.checkNo && pay.curAuditors && pay.uid === ctx.session.sessionUser.accountId) { %>
+                                    <a href="/sp/<%- ctx.subProject.id %>/financial/pay/<%- pay.id %>/detail?from=list" class="btn <%- auditConst.statusButtonClass[pay.status] %> btn-sm"><%- auditConst.statusButton[pay.status] %></a>
+                                <% } else if (pay.status === auditConst.status.checking && pay.curAuditors && pay.curAuditors.findIndex(x => { return x.aid === ctx.session.sessionUser.accountId; }) >= 0) { %>
+                                    <% const curAudit = pay.curAuditors.find(x => { return x.aid === ctx.session.sessionUser.accountId; }); %>
+                                    <% if (curAudit.status === auditConst.status.checking) { %>
+                                        <a href="/sp/<%- ctx.subProject.id %>/financial/pay/<%- pay.id %>/detail?from=list" class="btn <%- auditConst.statusButtonClass[pay.status] %> btn-sm"><%- auditConst.statusButton[pay.status] %></a>
+                                    <% } else { %>
+                                        <span class="<%- auditConst.auditStringClass[curAudit.status] %>"><%- auditConst.auditString[curAudit.status] %></span>
+                                    <% } %>
+                                <% } else { %>
+                                    <span class="<%- auditConst.auditStringClass[pay.status] %>"><%- auditConst.auditString[pay.status] %></span>
+                                <% } %>
+                                <% if (pay.uid === ctx.session.sessionUser.accountId && (pay.status === auditConst.status.uncheck || pay.status === auditConst.status.checkNo)) { %><a href="javascript:void(0);" data-id="<%- pay.id %>" class="text-danger del-pay-btn">删除</a><% } %>
+                            </td>
+                        </tr>
+                    <% } %>
+                    </tbody>
+                </table>
+                <!--翻页-->
+                <% include ../layout/page.ejs %>
+            </div>
+    </div>
+</div>
+<link href="/public/css/bootstrap/select2.min.css" rel="stylesheet" />
+<link rel="stylesheet" href="/public/css/bootstrap/select2-bootstrap4.min.css">
+<script src="/public/js/bootstrap/select2.min.js"></script>
+<style>
+    .select2-container {
+        /*display: inline-block!important;*/
+        /*height: 27px;*/
+        width: 100% !important;
+    }
+    .select2-container--bootstrap4 .select2-selection--single {
+        height: calc(0.9em + .75rem) !important;
+    }
+    /*.select2-container--bootstrap4 .select2-selection {*/
+    /*    background-color: #f8f9fa;*/
+    /*    !*border-color: #f8f9fa;*!*/
+    /*    border: 1px solid #f8f9fa;*/
+    /*}*/
+    /*.select2-container--bootstrap4 .select2-selection--single .select2-selection__rendered:focus{*/
+    /*    box-shadow: 0 0 0 0.2rem rgba(216,217,219,.5);*/
+    /*}*/
+    /*.select2-container--bootstrap4 .select2-selection--single .select2-selection__rendered:hover {*/
+    /*    background-color: #e2e6ea;*/
+    /*    border-color: #dae0e5;*/
+    /*}*/
+    .select2-container--bootstrap4 .select2-selection--single .select2-selection__rendered {
+        line-height: calc(0.9em + .75rem);
+        /*color: #007bff!important;*/
+        border-radius: 0.2rem;
+        /*background-color: #f8f9fa;*/
+        /*border-color: #f8f9fa;*/
+    }
+    .select2-container--bootstrap4 .select2-selection--single .select2-selection__arrow b {
+        border-color: #007bff transparent transparent transparent;
+        border-width: 4px 3.7px 0;
+    }
+    .select2-search--dropdown .select2-search__field {
+        padding: 0.175rem 0.5rem;
+    }
+</style>
+<script>
+    const user_id = <%- ctx.session.sessionUser.accountId %>;
+    const is_admin = <%- ctx.session.sessionUser.is_admin %>;
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenders)) %>'));
+    const userCompanyList = JSON.parse(unescape('<%- escape(JSON.stringify(userCompanyList)) %>'));
+    const fptReportTids = JSON.parse(unescape('<%- escape(JSON.stringify(fptReportTids)) %>'));
+    const auditConst = JSON.parse(unescape('<%- escape(JSON.stringify(auditConst)) %>'));
+    const auditType = JSON.parse(unescape('<%- escape(JSON.stringify(auditType)) %>'));
+    const selfCategoryLevel = '';
+    const pid = '<%- ctx.session.sessionProject.id %>';
+    const subProid = '<%- ctx.subProject.id %>';
+    const uphlname = 'user_' + user_id + '_pro_' + pid + '_sub_' + subProid + '_pay_category_hide_list';
+</script>

+ 49 - 0
app/view/financial/pay_list_modal.ejs

@@ -0,0 +1,49 @@
+<% include ../shares/delete_hint_modal.ejs %>
+<!--批量审批-->
+<div class="modal fade" id="batch-sp" 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">
+                <table class="table table-bordered">
+                    <tr><th>选择</th><th>标段名称</th><th>支付编号</th><th>收款单位</th><th>支付金额</th></tr>
+                    <tr><td><input type="checkbox"></td><td>TJ01</td><td>TJ01-20231207001</td><td>中国交通物资有限公司</td><td class="text-right">12345678</td></tr>
+                    <tr><td><input type="checkbox"></td><td>TJ01</td><td>TJ01-20231207001</td><td>中国交通物资有限公司</td><td class="text-right">12345678</td></tr>
+                    <tr><td><input type="checkbox"></td><td>TJ01</td><td>TJ01-20231207001</td><td>中国交通物资有限公司</td><td class="text-right">12345678</td></tr>
+                    <tr><td><input type="checkbox"></td><td>TJ01</td><td>TJ01-20231207001</td><td>中国交通物资有限公司</td><td class="text-right">12345678</td></tr>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-success">审批</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4 modal-height-500" style="overflow: auto">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush" id="auditor-list">
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto" id="audit-list">
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 27 - 115
app/view/financial/pay_modal.ejs

@@ -85,6 +85,33 @@
         });
     });
 </script>
+<% if (notStagePays.length > 0) { %>
+    <div class="modal fade" id="contract-old-pay" 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" style="max-height: 800px;overflow: auto;">
+                    <table class="table table-bordered">
+                        <tr class="text-center"><th>选择</th><th>标段名称</th><th>支付编号</th><th>申请人</th></tr>
+                        <% for (const pay of notStagePays) { %>
+                        <tr>
+                            <td class="text-center"><input type="checkbox" name="pays[]" value="<%- pay.id %>"></td>
+                            <td><%- pay.tenderName %></td>
+                            <td><a href="/sp/<%- ctx.subProject.id %>/financial/pay/<%- pay.id %>/detail" target="_blank"><%- pay.code %></a></td>
+                            <td class="text-center"><%- pay.username %></td>
+                        <% } %>
+                    </table>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                    <button type="button" class="btn btn-sm btn-primary" id="batch-old-pays">确定关联到本单位期</button>
+                </div>
+            </div>
+        </div>
+    </div>
+<% } %>
 <% } %>
 <!--批量审批-->
 <div class="modal fade" id="batch-sp" data-backdrop="static">
@@ -109,33 +136,6 @@
         </div>
     </div>
 </div>
-<!--付款账号-->
-<div class="modal fade" id="payaccount" 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">
-                <table class="table table-bordered">
-                    <tr><td>开户名称<b class="text-danger">*</b></td><td><input type="text" name="name" class="form-control form-control-sm"></td></tr>
-                    <tr><td>开户银行<b class="text-danger">*</b></td><td><input type="text" name="bank" class="form-control form-control-sm"></td></tr>
-                    <tr><td>开户账号<b class="text-danger">*</b></td><td><input type="text" name="bank_account" class="form-control form-control-sm"></td></tr>
-                    <tr><td>联系人</td><td><input type="text" name="contact" class="form-control form-control-sm"></td></tr>
-                    <tr><td>联系电话</td><td><input type="text" name="phone" class="form-control form-control-sm"></td></tr>
-                    </tbody>
-                </table>
-            </div>
-            <div class="modal-footer">
-                <input type="hidden" name="id" value="">
-                <input type="hidden" name="tid" value="">
-                <button type="button" class="btn btn-sm btn-primary mr-auto" id="get-form-tender">从计量标段同步</button>
-                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-sm btn-primary" id="set-pay-btn">确定添加</button>
-            </div>
-        </div>
-    </div>
-</div>
 <!--审批流程/结果-->
 <div class="modal fade" id="sp-list" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">
@@ -161,91 +161,3 @@
         </div>
     </div>
 </div>
-<% if (ctx.session.sessionUser.is_admin) { %>
-<div class="modal fade" id="liucheng" data-backdrop="static" style="z-index: 1049">
-    <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 pt-0">
-                <div class="row" style="min-height: 400px;max-height: 700px;">
-                    <div class="col-3" style="min-height: 400px;max-height: 700px;overflow:auto;">
-                        <div class="p-2">标段列表</div>
-                        <table class="table table-bordered">
-                            <thead>
-                            <tr class="text-center"><th width="50px">选择</th><th>标段名称</th></tr>
-                            </thead>
-                            <tbody id="shenpi-tender-list">
-                            <% for (const t of tenders) { %>
-                                <tr data-tid="<%- t.id %>"><td class="text-center"><input type="checkbox"></td><td class="change-tender" style="cursor: pointer;"><%- t.name %></td></tr>
-                            <% } %>
-                            </tbody>
-                        </table>
-                    </div>
-                    <div class="col-4">
-                        <div class="d-flex flex-row bg-graye">
-                            <div class="px-2 pt-1 dropdown">
-                                <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="report_audit_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                                    添加用户
-                                </button>
-                                <div class="dropdown-menu dropdown-menu-right" id="report_audit_dropdownMenu" aria-labelledby="report_audit_dropdownMenuButton" style="width:220px">
-                                    <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search" placeholder="姓名/手机 检索" data-code="report_audit" autocomplete="off"></div>
-                                    <dl class="list-unstyled book-list">
-                                        <% accountGroup.forEach((group, idx) => { %>
-                                            <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
-                                            <div class="dd-content" data-toggleid="<%- idx %>">
-                                                <% group.groupList.forEach(item => { %>
-                                                    <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
-                                                        <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
-                                                                    class="ml-auto"><%- item.mobile %></span></p>
-                                                        <span class="text-muted"><%- item.role %></span>
-                                                    </dd>
-                                                <% });%>
-                                            </div>
-                                        <% }) %>
-                                    </dl>
-                                </div>
-                            </div>
-                            <div class="p-2"><a href="javascript:void(0);" class="text-danger" id="batch-del-ptAudit">批量删除</a></div>
-                            <div class="p-2"><a href="javascript:void(0);" id="batch-other-ptAudit">同步勾选用户至其他已勾选标段</a></div>
-                        </div>
-                        <table class="table table-bordered">
-                            <thead>
-                            <tr class="text-center"><th><input type="checkbox" id="select-all-ptAudits"></th><th>用户名</th><th>单位</th><th>填报</th><th>操作</th></tr>
-                            </thead>
-                            <tbody id="report-list">
-                            </tbody>
-                        </table>
-                    </div>
-                    <div class="col-5">
-                        <div class="d-flex flex-row bg-graye">
-                            <div class="p-2"><a href="javascript:void(0);" id="set-other-tenders">设置流程至其他已勾选标段</a></div>
-                        </div>
-<!--                        <div>-->
-<!--                            <ul class="list-unstyled">-->
-<!--                                <li class="d-flex justify-content-start align-items-center">-->
-<!--                                    <span class="col-auto">填报人</span>-->
-<!--                                    <span class="col-10 spr-span" id="report-list">-->
-<!--                                    </span>-->
-<!--                                </li>-->
-<!--                            </ul>-->
-<!--                        </div>-->
-                        <div id="shenpi-list">
-                        </div>
-                    </div>
-                </div>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
-            </div>
-        </div>
-    </div>
-</div>
-<script>
-    const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
-    const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
-    const sp_status = JSON.parse('<%- JSON.stringify(shenpi.sp_status) %>');
-    const sp_type = JSON.parse('<%- JSON.stringify(shenpi.sp_other_type) %>');
-</script>
-<% } %>

+ 101 - 0
app/view/financial/pay_stage.ejs

@@ -0,0 +1,101 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main  d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div class="col-10 pl-0">
+                <div class="btn-group group-tab">
+                    <a class="btn btn-sm btn-light active" href="javascript:void(0);">支付列表</a>
+                    <a class="btn btn-sm btn-light account-page-size" href="/sp/<%- ctx.subProject.id %>/financial/pay/list">汇总列表</a>
+                    <a class="btn btn-sm btn-light" href="/financial/pay">标段统计</a>
+                </div>
+                <div class="d-inline-block col-sm-3">
+                    <div class="input-group input-group-sm pr-1">
+                        <select class="form-control form-control-sm col-auto" id="company_select">
+                            <option value="0">筛选单位</option>
+                            <% for (const c of userCompanyList) { %>
+                            <option value="<%- c.id %>" <% if (c.name === company) { %>selected<% } %>><%- c.name %></option>
+                            <% } %>
+                        </select>
+                    </div>
+                </div>
+                <div class="d-inline-block col-sm-2 pl-0">
+                    <div class="input-group input-group-sm pr-1">
+                        <select class="form-control form-control-sm col-auto" id="order_select">
+                            <option selected="0">筛选期数</option>
+                            <% for (const uo of userOrderList) { %>
+                            <option value="<%- uo %>" <% if (uo === qi) { %>selected<% } %>>第<%- uo %>期</option>
+                            <% } %>>
+                        </select>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <% if (ctx.session.sessionUser.is_admin || fptReportTids) { %>
+                    <a href="#payaccount" data-toggle="modal" data-target="#payaccount" class="btn btn-sm btn-primary mr-2">付款账号</a>
+                    <% } %>
+                    <% if (ctx.session.sessionUser.is_admin) { %>
+                    <a href="#liucheng" data-toggle="modal" data-target="#liucheng" class="btn btn-sm btn-primary mr-2">审批流程</a>
+                    <% } %>
+                </div>
+            </div>
+            <div class="d-inline-block ml-auto">
+                <% if (ctx.session.sessionUser.is_admin || fptReportTids.length > 0) { %>
+                <a href="#add-payqi" data-toggle="modal" data-target="#add-payqi" class="btn btn-primary btn-sm pull-right">新建支付期</a>
+                <% } %>
+<!--                <a href="#batch-sp" data-toggle="modal" data-target="#batch-sp" class="btn btn-success btn-sm pull-right mr-2">批量审批</a>-->
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="sjs-height-0" style="background-color: #fff">
+                <table class="table table-bordered text-center">
+                    <thead>
+                    <tr>
+                        <th width="100px">期数</th>
+                        <th width="300px">申请单位</th>
+                        <th width="150px">申请时间</th>
+                        <th width="100px">申请人</th>
+                        <th width="150px">本期金额</th>
+                        <th width="150px">截止上期金额</th>
+                        <th width="150px">截止本期金额</th>
+                        <th width="150px">操作</th>
+                    </tr>
+                    </thead>
+                    <tbody id="pay-list">
+                    <% for (const pay of payList) { %>
+                        <tr class="text-center" data-tid="<%- pay.order %>">
+                            <td class=""><a href="/sp/<%- ctx.subProject.id %>/financial/pay/stage/<%- pay.id %>" class="account-page-size">第<%- pay.order %>期</a></td>
+                            <td class="text-left"><%- pay.company %></td>
+                            <td class=""><%- moment(pay.create_time).format('YYYY-MM-DD') %></td>
+                            <td class=""><%- pay.username %></td>
+                            <td class="text-right"><%- pay.total_price %></td>
+                            <td class="text-right"><%- pay.pre_tp %></td>
+                            <td class="text-right"><%- ctx.helper.add(pay.total_price, pay.pre_tp) %></td>
+                            <td>
+                                <% if ((ctx.session.sessionUser.is_admin || pay.uid === ctx.session.sessionUser.accountId) && pay.can_del) { %>
+                                <a href="javascript:void(0);" data-id="<%- pay.id %>" class="text-danger del-pay-btn">删除</a>
+                                <% } %>
+                            </td>
+                        </tr>
+                    <% } %>
+                    </tbody>
+                </table>
+                <!--翻页-->
+                <% include ../layout/page.ejs %>
+            </div>
+    </div>
+</div>
+<script>
+    const user_id = <%- ctx.session.sessionUser.accountId %>;
+    const is_admin = <%- ctx.session.sessionUser.is_admin %>;
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenders)) %>'));
+    const userCompanyList = JSON.parse(unescape('<%- escape(JSON.stringify(userCompanyList)) %>'));
+    const fptReportTids = JSON.parse(unescape('<%- escape(JSON.stringify(fptReportTids)) %>'));
+    const auditConst = JSON.parse(unescape('<%- escape(JSON.stringify(auditConst)) %>'));
+    const auditType = JSON.parse(unescape('<%- escape(JSON.stringify(auditType)) %>'));
+    const selfCategoryLevel = '';
+    const pid = '<%- ctx.session.sessionProject.id %>';
+    const subProid = '<%- ctx.subProject.id %>';
+    const uphlname = 'user_' + user_id + '_pro_' + pid + '_sub_' + subProid + '_pay_category_hide_list';
+</script>

+ 165 - 0
app/view/financial/pay_stage_modal.ejs

@@ -0,0 +1,165 @@
+<% include ../shares/delete_hint_modal.ejs %>
+<% if (ctx.session.sessionUser.is_admin || fptReportTids.length > 0) { %>
+<!-- 申请支付 -->
+<div class="modal fade show" id="add-payqi" 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">
+                <div class="form-group mb-2">
+                    <label>申请单位<strong class="text-danger">*</strong></label>
+                    <% if (ctx.session.sessionUser.is_admin) { %>
+                    <select name="company" class="form-control form-control-sm" id="add-company-select">
+                        <% for (const c of userCompanyList) { %>
+                            <option value="<%- c.name %>" <% if (c.name === company) { %>selected<% } %>><%- c.name %></option>
+                        <% } %>
+                    </select>
+                    <% } else { %>
+                    <input class="form-control form-control-sm" id="add-company-select" name="company" placeholder="创建人所属单位" value="<%- user.company %>" type="text" readonly="">
+                    <% } %>
+                </div>
+                <div class="form-group mb-2">
+                    <label>支付期数</label>
+                    <input class="form-control form-control-sm" id="company_order" placeholder="自动根据单位生成的期数" value="<%- (ctx.session.sessionUser.is_admin && company ? userCompanyList.find(item => item.name === company).count + 1 : (user.company ? userCompanyList.find(item => item.name === user.company).count + 1 : 0)) %>" type="text" readonly="">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm  btn-primary" id="add-qi-btn">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>
+<!--批量审批-->
+<div class="modal fade" id="batch-sp" 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">
+                <table class="table table-bordered">
+                    <tr><th>选择</th><th>标段名称</th><th>支付编号</th><th>收款单位</th><th>支付金额</th></tr>
+                    <tr><td><input type="checkbox"></td><td>TJ01</td><td>TJ01-20231207001</td><td>中国交通物资有限公司</td><td class="text-right">12345678</td></tr>
+                    <tr><td><input type="checkbox"></td><td>TJ01</td><td>TJ01-20231207001</td><td>中国交通物资有限公司</td><td class="text-right">12345678</td></tr>
+                    <tr><td><input type="checkbox"></td><td>TJ01</td><td>TJ01-20231207001</td><td>中国交通物资有限公司</td><td class="text-right">12345678</td></tr>
+                    <tr><td><input type="checkbox"></td><td>TJ01</td><td>TJ01-20231207001</td><td>中国交通物资有限公司</td><td class="text-right">12345678</td></tr>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-success">审批</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--付款账号-->
+<div class="modal fade" id="payaccount" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">付款账号(标段名称)</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row" style="min-height: 400px;max-height: 700px;">
+                    <div class="col-5" style="min-height: 400px;max-height: 700px;overflow:auto;">
+                        <div class="p-2">标段列表</div>
+                        <div id="pay-tender-list"></div>
+                    </div>
+                    <div class="col-7">
+                        <div class="d-flex">
+                            <a href="javascript:void(0);" class="text-primary mr-3 mb-1" id="get-form-tender">从计量标段同步</a>
+                            <a href="javascript:void(0);" class="text-primary mb-1" id="batch-other-bank">同步信息到所选标段</a>
+                        </div>
+                        <table class="table table-bordered">
+                            <tr><td>开户名称<b class="text-danger">*</b></td><td><input type="text" name="name" class="form-control form-control-sm"></td></tr>
+                            <tr><td>开户银行<b class="text-danger">*</b></td><td><input type="text" name="bank" class="form-control form-control-sm"></td></tr>
+                            <tr><td>开户账号<b class="text-danger">*</b></td><td><input type="text" name="bank_account" class="form-control form-control-sm"></td></tr>
+                            <tr><td>联系人</td><td><input type="text" name="contact" class="form-control form-control-sm"></td></tr>
+                            <tr><td>联系电话</td><td><input type="text" name="phone" class="form-control form-control-sm"></td></tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="id" value="">
+                <input type="hidden" name="tid" value="">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% if (ctx.session.sessionUser.is_admin) { %>
+<div class="modal fade" id="liucheng" data-backdrop="static" style="z-index: 1049">
+    <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 pt-0">
+                <div class="row" style="min-height: 400px;max-height: 700px;">
+                    <div class="col-4" style="min-height: 400px;max-height: 700px;overflow:auto;">
+                        <div class="p-2">标段列表</div>
+                        <div id="shenpi-tender-list"></div>
+                    </div>
+                    <div class="col-4">
+                        <div class="d-flex flex-row bg-graye">
+                            <div class="px-2 pt-1 dropdown">
+                                <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="report_audit_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                    添加用户
+                                </button>
+                                <div class="dropdown-menu dropdown-menu-right" id="report_audit_dropdownMenu" aria-labelledby="report_audit_dropdownMenuButton" style="width:220px">
+                                    <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search" placeholder="姓名/手机 检索" data-code="report_audit" autocomplete="off"></div>
+                                    <dl class="list-unstyled book-list">
+                                        <% accountGroup.forEach((group, idx) => { %>
+                                            <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                            <div class="dd-content" data-toggleid="<%- idx %>">
+                                                <% group.groupList.forEach(item => { %>
+                                                    <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
+                                                        <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                    class="ml-auto"><%- item.mobile %></span></p>
+                                                        <span class="text-muted"><%- item.role %></span>
+                                                    </dd>
+                                                <% });%>
+                                            </div>
+                                        <% }) %>
+                                    </dl>
+                                </div>
+                            </div>
+                            <div class="p-2"><a href="javascript:void(0);" class="text-danger" id="batch-del-ptAudit">批量删除</a></div>
+                            <div class="p-2"><a href="javascript:void(0);" id="batch-other-ptAudit">同步勾选用户至其他已勾选标段</a></div>
+                        </div>
+                        <table class="table table-bordered">
+                            <thead>
+                            <tr class="text-center"><th><input type="checkbox" id="select-all-ptAudits"></th><th>用户名</th><th>单位</th><th>填报</th><th>操作</th></tr>
+                            </thead>
+                            <tbody id="report-list">
+                            </tbody>
+                        </table>
+                    </div>
+                    <div class="col-4">
+                        <div class="d-flex flex-row bg-graye">
+                            <div class="p-2"><a href="javascript:void(0);" id="set-other-tenders">设置流程至其他已勾选标段</a></div>
+                        </div>
+                        <div id="shenpi-list">
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+    const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+    const sp_status = JSON.parse('<%- JSON.stringify(shenpi.sp_status) %>');
+    const sp_type = JSON.parse('<%- JSON.stringify(shenpi.sp_other_type) %>');
+</script>
+<% } %>

+ 1 - 1
app/view/financial/sub_menu_list.ejs

@@ -11,7 +11,7 @@
 <div class="nav-box">
     <ul class="nav-list list-unstyled">
         <li class="<% if (ctx.url.indexOf('/pay') !== -1) { %>active<% } %>">
-            <a href="/sp/<%- ctx.subProject.id %>/financial/pay" class="account-page-size"><span class="ml-3">资金支付</span></a>
+            <a href="/sp/<%- ctx.subProject.id %>/financial/pay/stage" class="account-page-size"><span class="ml-3">资金支付</span></a>
         </li>
     </ul>
 </div>

+ 12 - 8
app/view/financial/transfer.ejs

@@ -17,15 +17,17 @@
                 <table class="table table-hover table-bordered text-center">
                     <thead>
                     <tr>
-                        <th width="100px">次数</th>
+                        <th width="50px">次数</th>
                         <th width="10%">划拨年月</th>
-                        <th width="10%">划拨金额</th>
-                        <th width="10%">填报人</th>
-                        <th>填报单位</th>
+                        <th width="120px">本次划拨金额</th>
+                        <th width="120px">截止上次划拨金额</th>
+                        <th width="120px">截止本次划拨金额</th>
+                        <th width="100px">填报人</th>
+                        <th width="150px">填报单位</th>
                         <th width="150px">填报时间</th>
-                        <th width="15%">填报备注</th>
-                        <th width="150px">附件</th>
-                        <th width="150px">操作</th>
+                        <th width="20%">填报备注</th>
+                        <th width="80px">附件</th>
+                        <th width="80px">操作</th>
                     </tr>
                     </thead>
                     <tbody id="transfer-list">
@@ -33,8 +35,10 @@
                     <% for (const [i, t] of transferList.entries()) { %>
                     <tr class="text-center" data-id="<%- t.id %>">
                         <td class=""><%- (transferList.length - i) %></td>
-                        <td class=""><a href="/sp/<%- ctx.subProject.id %>/financial/transfer/<%- t.id %>/tender" class="account-page-size"><%- t.t_time %></a></td>
+                        <td class=""><a href="/sp/<%- ctx.subProject.id %>/financial/transfer/<%- t.id %>/tender"><%- t.t_time %></a></td>
                         <td class="text-right"><%- t.total_price %></td>
+                        <td class="text-right"><%- t.pre_hb_tp %></td>
+                        <td class="text-right"><%- ctx.helper.add(t.total_price, t.pre_hb_tp) %></td>
                         <td class=""><%- t.username %></td>
                         <td class="text-left" ><%- t.company %></td>
                         <td class="" ><%- moment(t.create_time).format('YYYY-MM-DD HH:mm:ss') %></td>

+ 11 - 46
app/view/financial/transfer_tender.ejs

@@ -11,57 +11,15 @@
                 <a href="#unlock" data-toggle="modal" data-target="#unlock" class="btn btn-danger btn-sm pull-right">解锁数据</a>
                 <% } else { %>
                 <a href="#lock" data-toggle="modal" data-target="#lock" class="btn btn-success btn-sm pull-right mr-2">锁定数据</a>
-                <a href="#add-bd" data-toggle="modal" data-target="#add-bd" class="btn btn-primary btn-sm pull-right mr-2">添加标段</a>
+                <a href="#add-bd" data-toggle="modal" data-target="#add-ftt" class="btn btn-primary btn-sm pull-right mr-2">添加标段</a>
                 <% } %>
                 <% } %>
             </div>
         </div>
     </div>
     <div class="content-wrap">
-        <div class="c-body">
-            <div class="sjs-height-0">
-                <table class="table table-bordered">
-                    <thead class="text-center">
-                    <tr>
-                        <th width="50px">序号</th>
-                        <th width="">标段名称</th>
-                        <th width="130px">计量期数</th>
-                        <th width="100px">合同金额</th>
-                        <th width="100px">本期合同计量</th>
-                        <th width="100px">本期变更计量</th>
-                        <th width="100px">本期完成计量</th>
-                        <th width="100px">本期应付</th>
-                        <th width="100px">本期实付</th>
-                        <th width="100px">本次划拨金额</th>
-                        <th width="80px">附件</th>
-                        <% if (transferInfo.uid === ctx.session.sessionUser.accountId) { %>
-                        <th width="80px">操作</th>
-                        <% } %>
-                    </tr>
-                    </thead>
-                    <tbody id="tender-list">
-                    <% if (transferTenderList.length > 0) { %>
-                    <% for (const [i, t] of transferTenderList.entries()) { %>
-                    <tr class="text-right" data-id="<%- t.id %>">
-                        <td class="text-center"><%- i+1 %></td>
-                        <td class="text-left"><%- t.name %></td>
-                        <td class="text-center">第<%- t.sorder %>期</td>
-                        <td class="" ><%- t.total_price %></td>
-                        <td class="" ><%- t.contract_tp %></td>
-                        <td class="" ><%- t.qc_tp %></td>
-                        <td class="" ><%- ctx.helper.sum([t.contract_tp, t.qc_tp, t.pc_tp]) %></td>
-                        <td class="" ><%- t.yf_tp %></td>
-                        <td class="" ><%- t.sf_tp %></td>
-                        <td><% if (transferInfo.uid === ctx.session.sessionUser.accountId && !transferInfo.is_lock) { %><input type="text" class="form-control form-control-sm text-right" placeholder="默认等于本期实付" data-ftid="<%- t.id %>" value="<%- t.hb_tp %>"><% } else { %><%- t.hb_tp %><% } %></td>
-                        <td class="text-center" ><a href="javascript:void(0);" class="text-primary open-tender-files" data-ftid="<%- t.id %>"><i class="fa fa-paperclip fa-rotate-90"></i></a> <span class="file-num"><%- t.files.length > 0 ? t.files.length : '' %></span></td>
-                        <% if (transferInfo.uid === ctx.session.sessionUser.accountId) { %><td class="text-center"><% if (!transferInfo.is_lock) { %><a class="text-danger del-tender-btn" href="javascript:void(0);" data-id="<%- t.id %>">移除</a><% } %></td><% } %>
-                    </tr>
-                    <% } %>
-                    <% } %>
-                    </tbody>
-                </table>
-                <!--翻页-->
-                <% include ../layout/page.ejs %>
+        <div class="sjs-height-0" style="background-color: #fff">
+            <div class="c-body" id="tender-list">
             </div>
         </div>
     </div>
@@ -71,6 +29,13 @@
     const user_id = <%- ctx.session.sessionUser.accountId %>;
     const trid = '<%- transferInfo.id %>';
     const whiteList = JSON.parse(unescape('<%- escape(JSON.stringify(whiteList)) %>'));
-    const tenderList = JSON.parse(unescape('<%- escape(JSON.stringify(transferTenderList)) %>'));
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(transferTenderList)) %>'));
+    const transferInfo = JSON.parse(unescape('<%- escape(JSON.stringify(transferInfo)) %>'));
     const financialPermission = JSON.parse(unescape('<%- escape(JSON.stringify(financialPermission)) %>'));
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
+    const tenderList = JSON.parse(unescape('<%- escape(JSON.stringify(tenders)) %>'));
+    const selfCategoryLevel = '';
+    const pid = '<%- ctx.session.sessionProject.id %>';
+    const subProid = '<%- ctx.subProject.id %>';
+    const uphlname = 'user_' + user_id + '_pro_' + pid + '_sub_' + subProid + '_transfer_category_hide_list';
 </script>

+ 2 - 26
app/view/financial/transfer_tender_modal.ejs

@@ -38,37 +38,13 @@
 </div>
 <% } %>
 <!--添加标段-->
-<div class="modal fade" id="add-bd" data-backdrop="static">
+<div class="modal fade" id="add-ftt" 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" style="max-height: 500px;overflow: auto;">
-                <table class="table table-bordered">
-                    <thead class="text-center">
-                    <tr>
-                        <th width="40px">选择</th>
-                        <th width="">标段名称</th>
-                        <th width="150px">选择期数</th>
-                    </tr>
-                    </thead>
-                    <tbody id="tenders">
-                    <% for (const t of tenders) { %>
-                    <tr class="text-center">
-                        <td><input type="checkbox" value="<%- t.id %>"></td>
-                        <td class="text-left"><%- t.name %></td>
-                        <td>
-                            <select class="form-control form-control-sm selectpicker" title="选择期" data-width="150px" multiple>
-                                <% for (const s of t.stages) { %>
-                                    <option value="<%- s.order %>">第<%- s.order %>期</option>
-                                <% } %>
-                            </select>
-                        </td>
-                    </tr>
-                    <% } %>
-                    </tbody>
-                </table>
+            <div class="modal-body pt-0 mt-1" style="max-height: 500px;overflow: auto;" id="add-tender-list">
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>

+ 1 - 1
app/view/profile/netcasign.ejs

@@ -54,7 +54,7 @@
                                 <div class="form-group show-upload" style="display: none">
                                     <label>上传签名图</label>
                                     <input type="file" class="form-control-file" id="netcasign-upload">
-                                    <small class="form-text text-danger">建议签名图片大小为160x160px,四边满版裁切、无留白,格式PNG透明背景(适用于报表打印,签章直径为4.2厘米的情况)</small>
+                                    <small class="form-text text-danger">图片大小为600x300,格式PNG透明背景。</small>
                                 </div>
                                 <div class="form-group show-qrcode">
                                     <label>在线手写签名</label>

+ 1 - 1
app/view/profile/sign.ejs

@@ -32,7 +32,7 @@
                             <div class="form-group show-upload" style="display: none">
                                 <label>上传签名图</label>
                                 <input type="file" class="form-control-file" id="sign-upload">
-                                <small class="form-text text-danger">建议签名图片大小为160x160px,四边满版裁切、无留白,格式PNG透明背景(适用于报表打印,签章直径为4.2厘米的情况)</small>
+                                <small class="form-text text-danger">图片大小为600x300,格式PNG透明背景。</small>
                             </div>
                             <div class="form-group show-qrcode">
                                 <label>在线手写签名</label>

+ 0 - 0
app/view/quality/lab_modal.ejs


+ 32 - 7
app/view/stage/modal.ejs

@@ -657,16 +657,41 @@
                 <h5 class="modal-title">帮助</h5>
             </div>
             <div class="modal-body">
-                <div class="modal-height-250">
-                    <label for="">1、本期补差计算公式</label>
+                <div class="modal-height-400">
+                    <label>1、本期补差计算公式</label>
                     <div class="in-2">
-                        <label for="">本期补差=本期合同补差+本期变更补差。</label>
-                        <label>本期合同补差:新单价*截止上期合同数量-截止上期合同计量金额。</label>
-                        <label for="">本期变更补差:新单价*截止上期变更数量-截止上期变更计量金额。</label>
+                        <label>(1)合同类型:单价合同</label>
+                        <div class="in-2">
+                            <label>本期补差=本期合同补差+本期变更补差。</label>
+                            <label>本期合同补差:新单价*截止上期合同数量-截止上期合同计量金额。</label>
+                            <label>本期变更补差:新单价*截止上期变更数量-截止上期变更计量金额。</label>
+                        </div>
+                    </div>
+                    <div class="in-2">
+                        <label>(2)合同类型:总价合同</label>
+                        <div class="in-2">
+                            <label>本期补差=本期变更补差。</label>
+                            <label>本期变更补差:新单价*截止上期变更数量-截止上期变更计量金额。</label>
+                        </div>
+                    </div>
+                    <label> 2、变更后计算公式</label>
+                    <div class="in-2">
+                        <label>(1)合同类型:单价合同</label>
+                        <div class="in-2">
+                            <label>变更后数量=台账数量+变更令批复计价数量+变更令批复不计价数量。</label>
+                            <label>变更后金额=台账金额+变更令批复计价金额+变更令批复不计价金额。</label>
+                        </div>
+                    </div>
+                    <div class="in-2">
+                        <label>(2)合同类型:总价合同</label>
+                        <div class="in-2">
+                            <label>变更后数量=台账数量+变更令批复计价数量+变更令批复不计价数量。</label>
+                            <label>变更后金额=台账金额+变更令批复计价金额。</label>
+                        </div>
                     </div>
-                    <label for=""> 2、反算本期合同计量金额</label>
+                    <label> 3、反算本期合同计量金额</label>
                     <div class="in-2">
-                        <label for="">开启此选项,清单最后一次合同计量时,本期合同计量金额=截止本期合同数量*单价-截止上期合同计量金额。</label>
+                        <label>开启此选项,清单最后一次合同计量时,本期合同计量金额=截止本期合同数量*单价-截止上期合同计量金额。</label>
                     </div>
                 </div>
             </div>

+ 423 - 0
app/view/wap/inspection.ejs

@@ -0,0 +1,423 @@
+<!DOCTYPE html>
+<html lang="zh">
+<head>
+    <meta charset="UTF-8">
+    <title>质量巡检单生成助手</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" href="/public/css/bootstrap/bootstrap.min.css">
+    <link rel="stylesheet" href="/public/css/wap/main.css">
+    <link rel="stylesheet" href="/public/css/toast.css">
+    <link rel="stylesheet" href="/public/css/font-awesome/font-awesome.min.css">
+    <link rel="stylesheet" href="/public/css/toastr.css">
+    <link rel="shortcut icon" href="/public/images/favicon.ico">
+    <style>
+        body,
+        html {
+            height: 100%;
+            overflow: hidden;
+            margin: 0; /* 去掉默认 margin */
+            padding: 0;
+            -webkit-overflow-scrolling: touch;
+        }
+        body {
+            background: #f1f3f5;
+            font-size: 16px;
+            padding-bottom: 100px;
+        }
+        .chat-box {
+            height: calc(100vh - 100px); /* 减去头部和底部高度,150px 是示例,需根据实际调整 */
+            overflow-y: auto;
+            /*padding: 10px;*/
+            box-sizing: border-box;
+        }
+        .message {
+            display: flex;
+            margin-bottom: 12px;
+            padding: 0 15px;
+        }
+        .message.user {
+            justify-content: flex-end;
+        }
+        .message.ai {
+            justify-content: flex-start;
+        }
+        .bubble {
+            max-width: 95%;
+            padding: 10px 14px;
+            border-radius: 18px;
+            line-height: 1.4;
+            word-wrap: break-word;
+        }
+        .message.user .bubble {
+            background: #007bff;
+            color: white;
+            border-bottom-right-radius: 0;
+        }
+        .message.ai .bubble {
+            background: #dee2e6;
+            color: #333;
+            border-bottom-left-radius: 0;
+        }
+        .chat-footer {
+            position: fixed;
+            bottom: 0;
+            left: 0;
+            right: 0;
+            width: 100%;
+            background: #fff;
+            padding: 22px env(safe-area-inset-right) calc(10px + env(safe-area-inset-bottom)) env(safe-area-inset-left);
+            box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.1);
+            z-index: 999;
+        }
+
+        .chat-footer .input-group {
+            bottom: 22px;
+        }
+
+        .chat-footer input.form-control {
+            border: none;
+            padding: 10px 12px;
+            height: 44px;
+        }
+        .chat-footer .btn {
+            border-radius: 0;
+        }
+
+        .form-preview {
+            background: #fff;
+            border: 1px solid #dee2e6;
+            border-radius: 12px;
+            padding: 15px 20px;
+            margin: 20px 15px 0 15px;
+            box-shadow: 0 2px 6px rgba(0,0,0,0.05);
+        }
+        .form-preview h6 {
+            font-weight: bold;
+            margin-bottom: 16px;
+            border-bottom: 1px solid #dee2e6;
+            padding-bottom: 8px;
+            font-size: 17px;
+        }
+        .form-item {
+            margin-bottom: 12px;
+            font-size: 15px;
+            line-height: 1.5;
+        }
+        .form-item label {
+            font-weight: 500;
+            color: #555;
+        }
+        .form-item span {
+            display: inline-block;
+            margin-left: 5px;
+            color: #333;
+        }
+        .form-actions {
+            border-top: 1px solid #dee2e6;
+            padding-top: 12px;
+            margin-top: 16px;
+            display: flex;
+            justify-content: space-between;
+        }
+        /* 1. WebKit 浏览器(Chrome, Safari, Android) */
+        #chat-box::-webkit-scrollbar {
+            width: 4px;
+        }
+
+        #chat-box::-webkit-scrollbar-track {
+            background: transparent;
+        }
+
+        #chat-box::-webkit-scrollbar-thumb {
+            background-color: rgba(0, 0, 0, 0.3);
+            border-radius: 4px;
+        }
+
+        /* 2. Firefox */
+        #chat-box {
+            scrollbar-width: thin;
+            scrollbar-color: rgba(0, 0, 0, 0.3) transparent;
+        }
+
+        /* 3. 选项:在小屏上彻底隐藏滚动条(可滑动但看不见) */
+        @media (max-width: 768px) {
+            #chat-box::-webkit-scrollbar {
+                display: none;
+            }
+            #chat-box {
+                -ms-overflow-style: none; /* IE 10+ */
+                scrollbar-width: none;    /* Firefox */
+            }
+        }
+        .input-group {
+            position: relative;
+        }
+
+        .input-icon {
+            position: absolute;
+            right: 75px; /* 根据按钮宽度调整 */
+            top: 50%;
+            transform: translateY(-50%);
+            cursor: pointer;
+            color: #888;
+            z-index: 10;
+        }
+
+        #voice-icon.active {
+            color: #007bff;
+        }
+        .xj-title {
+            position: sticky;
+            top: 0;
+            background: #fff;
+        }
+
+        #recording-toast {
+            position: fixed;
+            bottom: 80px;
+            left: 50%;
+            transform: translateX(-50%);
+            background-color: rgba(0, 0, 0, 0.85);
+            color: #fff;
+            padding: 12px 20px;
+            border-radius: 24px;
+            font-size: 14px;
+            display: flex;
+            align-items: center;
+            z-index: 9999;
+            animation: fadeInUp 0.3s ease-out;
+        }
+
+        #recording-toast .fa {
+            margin-right: 8px;
+        }
+
+        @keyframes fadeInUp {
+            from {
+                opacity: 0;
+                transform: translate(-50%, 100%);
+            }
+            to {
+                opacity: 1;
+                transform: translate(-50%, 0);
+            }
+        }
+    </style>
+</head>
+<body>
+<div class="container mb-3 px-0">
+    <h5 class="text-center py-2 xj-title mb-0">质量巡检单生成助手</h5>
+    <div id="chat-box" class="chat-box mb-3">
+        <div id="messages"></div>
+        <!-- 表单预览卡片 -->
+        <div id="form-preview" class="form-preview d-none">
+            <h6><i class="fa fa-clipboard-check mr-2"></i>巡检单信息确认</h6>
+
+            <div class="form-item">
+                <label>检查项目:</label>
+                <span id="field-check_item">—</span>
+            </div>
+            <div class="form-item">
+                <label>现场检查情况:</label>
+                <span id="field-check_situation">—</span>
+            </div>
+            <div class="form-item">
+                <label>处理要求及措施:</label>
+                <span id="field-action">—</span>
+            </div>
+            <div class="form-item">
+                <label>检查日期:</label>
+                <span id="field-date">—</span>
+            </div>
+            <div class="form-item">
+                <label>质检员:</label>
+                <span id="field-inspector">—</span>
+            </div>
+
+            <div class="form-actions">
+                <button id="modify-btn" class="btn btn-outline-secondary btn-sm">
+                    <i class="fa fa-edit"></i> 修改
+                </button>
+                <button id="confirm-btn" class="btn btn-success btn-sm">
+                    <i class="fa fa-check-circle"></i> 确认生成
+                </button>
+            </div>
+        </div>
+    </div>
+    <div class="chat-footer">
+        <div class="input-group">
+            <input type="text" id="user-input" class="form-control" placeholder="请输入...">
+            <div class="input-icon">
+                <i id="voice-icon" class="fa fa-microphone"></i>
+            </div>
+            <div class="input-group-append">
+                <button class="btn btn-primary btn-send" id="send-btn">发送</button>
+            </div>
+        </div>
+    </div>
+</div>
+<div id="recording-toast" style="display: none;">
+    <div class="toast-inner">
+        <i class="fa fa-microphone"></i>
+        <span>正在录音,请讲话...</span>
+    </div>
+</div>
+<script src="/public/js/jquery/jquery-3.2.1.min.js"></script>
+<script src="/public/js/popper/popper.min.js"></script>
+<script src="/public/js/bootstrap/bootstrap.min.js"></script>
+<script src="/public/js/toastr.min.js"></script>
+<script src="/public/js/cookies.js"></script>
+<script src="/public/js/wap/global.js"></script>
+<script>
+    const csrf = '<%= ctx.csrf %>';
+</script>
+<script>
+    const $chatBox = $('#chat-box');
+    const $formPreview = $('#form-preview');
+    let conversationId = '';
+    $(function () {
+        const appendMessage = (text, sender = 'user', cssClass = '') => {
+            const formatted = text.replace(/\n/g, '<br>');
+            const $message = $('<div class="message"></div>').addClass(sender);
+            if (cssClass) {
+                $message.addClass(cssClass);
+            }
+            const $bubble = $('<div class="bubble"></div>').html(formatted);
+            $message.append($bubble);
+            $('#messages').append($message);
+            $chatBox.scrollTop($chatBox[0].scrollHeight);
+        };
+        appendMessage("👋 嗨,我是质量巡检单生成助手,告诉我项目名称可以帮你快速生成巡检单", "ai", 'mt-3');
+        const updatePreviewForm = (data) => {
+            $('#field-check_item').text(data.check_item || '—');
+            $('#field-check_situation').text(data.check_situation || '—');
+            $('#field-action').text(data.action || '—');
+            $('#field-date').text(data.date || '—');
+            $('#field-inspector').text(data.inspector || '—');
+            $formPreview.removeClass('d-none');
+        };
+
+        $('#send-btn').on('click', () => {
+            const input = $('#user-input').val().trim();
+            if (!input) return;
+            appendMessage(input, 'user');
+            $('#user-input').val('');
+            $formPreview.addClass('d-none');
+
+            postData('/wap/inspection/ask', { user_input: input, conversationId }, function (result) {
+                if (result) {
+                    conversationId = result.conversation_id;
+                    if (result.answer_text) {
+                        appendMessage(result.answer_text, 'ai');
+                    }
+                    if (result.answer_json) {
+                        updatePreviewForm(result.answer_json);
+                        $formPreview.removeClass('d-none');
+                    } else {
+                        $formPreview.addClass('d-none');
+                    }
+                } else {
+                    appendMessage('请求出错,请稍后重试。', 'ai');
+                }
+            }, function () {
+                appendMessage('请求出错,请再输入一次。', 'ai');
+            });
+        });
+
+        $('#user-input').on('keypress', function(e) {
+            if (e.which === 13) $('#send-btn').click();
+        });
+
+        $('#confirm-btn').on('click', () => {
+            alert('✅ 表单已提交!你可以跳转页面或保存到数据库');
+            // 可跳转页面如 window.location.href = '/form/preview';
+        });
+
+        $('#modify-btn').on('click', () => {
+            $formPreview.addClass('d-none');
+            appendMessage('请继续修改你想调整的信息。', 'ai');
+        });
+
+        const originalHeight = window.innerHeight;
+        $('#user-input').on('focus', function () {
+            const $input = $(this);
+
+            const checkKeyboardOpen = function () {
+                const newHeight = window.innerHeight;
+                if (newHeight < originalHeight) {
+                    // 键盘已弹出
+                    setTimeout(function () {
+                        $input[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
+                    }, 100);
+                    $(window).off('resize', checkKeyboardOpen);
+                }
+            };
+
+            $(window).on('resize', checkKeyboardOpen);
+        });
+
+        const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
+        recognition.continuous = false;
+        recognition.lang = 'zh-CN';
+        recognition.interimResults = false;
+
+        let isRecording = false;
+        let pressTimer = null;
+
+        $('#voice-icon').on('touchstart', function (e) {
+            e.preventDefault();
+            $(this).addClass('active');
+            // if (!isRecording) {
+            //     recognition.start();
+            //     $(this).addClass('active');
+            //     $('#recording-toast').fadeIn(); // 👉 显示录音提示
+            //     isRecording = true;
+            // }
+            pressTimer = setTimeout(() => {
+                recognition.start();
+                isRecording = true;
+                $('#recording-toast').fadeIn(); // 👉 显示录音提示
+            }, 300); // 按住 300ms 开始录音,可根据体验调整
+        });
+
+        $('#voice-icon').on('touchend touchcancel', function (e) {
+            e.preventDefault();
+            clearTimeout(pressTimer);
+            if (isRecording) {
+                recognition.stop();
+                isRecording = false;
+                $('#recording-toast').fadeOut(); // 👉 隐藏录音提示
+            }
+            $(this).removeClass('active');
+        });
+
+        recognition.onresult = function (event) {
+            const result = event.results[0][0].transcript.trim();
+            $('#user-input').val(result);
+        };
+
+        recognition.onerror = function (err) {
+            console.error('语音识别出错:', err);
+            $('#voice-icon').removeClass('active');
+            $('#recording-toast').fadeOut(); // 👉 出错时也隐藏
+            isRecording = false;
+        };
+
+        recognition.onend = function () {
+            $('#voice-icon').removeClass('active');
+            $('#recording-toast').fadeOut(); // 👉 识别结束时隐藏
+            isRecording = false;
+        };
+
+        if (isWeChat()) {
+            $('#voice-icon').hide(); // 在微信内隐藏语音按钮
+        }
+    });
+
+    function isWeChat() {
+        const ua = navigator.userAgent.toLowerCase();
+        return /micromessenger/.test(ua);
+    }
+</script>
+</body>
+</html>

+ 42 - 0
config/web.js

@@ -1899,10 +1899,37 @@ const JsFiles = {
                     '/public/js/sub_menu.js',
                     '/public/js/div_resizer.js',
                     '/public/js/zh_calc.js',
+                    '/public/js/PinYinOrder.bundle.js',
+                    '/public/js/colResizable/colResizable-1.6.min.js',
+                    '/public/js/shares/tender_list_order.js',
+                    '/public/js/shares/show_level.js',
+                    '/public/js/tender_showhide.js',
+                    '/public/js/tender_list_base.js',
                     '/public/js/financial_transfer_tender.js',
                 ],
                 mergeFile: 'financial_transfer_tender',
             },
+            payStage: {
+                files: [
+                    '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
+                    '/public/js/component/menu.js',
+                    '/public/js/moment/moment.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/PinYinOrder.bundle.js',
+                    '/public/js/colResizable/colResizable-1.6.min.js',
+                    '/public/js/shares/tender_list_order.js',
+                    '/public/js/shares/show_level.js',
+                    '/public/js/tender_showhide.js',
+                    '/public/js/tender_list_base.js',
+                    '/public/js/financial_pay_stage.js',
+                ],
+                mergeFile: 'financial_pay_stage',
+            },
             pay: {
                 files: [
                     '/public/js/decimal.min.js',
@@ -1918,6 +1945,21 @@ const JsFiles = {
                 ],
                 mergeFile: 'financial_pay',
             },
+            payList: {
+                files: [
+                    '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
+                    '/public/js/component/menu.js',
+                    '/public/js/moment/moment.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/financial_pay_list.js',
+                ],
+                mergeFile: 'financial_pay_list',
+            },
             payDetail: {
                 files: [
                     '/public/js/decimal.min.js',

+ 80 - 0
db_script/financial_transfer.js

@@ -0,0 +1,80 @@
+/*
+ 修改脚本,请现在指定项目测试
+ 例如: node db_script/sub_project local T201711273363
+ 没有意外再全部执行
+
+ 所有修改均应考虑脚本二次执行时的兼容,应检查是否已执行过,避免在生产环境运行时出现问题后需要二次执行,参见自定义分类脚本
+  */
+const BaseUtil = require('./baseUtils');
+const querySql = BaseUtil.querySql;
+const _ = require('lodash');
+const ZhCalc = BaseUtil.ZhCalc;
+
+const calcPreHbTP = async function(subProject) {
+    const ftList = await querySql('SELECT * FROM zh_financial_transfer where spid = ? ORDER BY id DESC', [subProject.id]);
+    if (ftList.length > 0) {
+        const updateFtList = [];
+        const updateFttList = [];
+        const fttList = await querySql('SELECT * FROM zh_financial_transfer_tender where spid = ? ORDER BY trid DESC, id ASC', [subProject.id]);
+        if (fttList.length > 0) {
+            for (const ftt of fttList) {
+                const ftts = _.filter(fttList, function(item) {
+                    return item.tid === ftt.tid && item.trid < ftt.trid;
+                });
+                let pre_hb_tp = 0;
+                if (ftts.length > 0) {
+                    pre_hb_tp = ZhCalc.sum(_.map(ftts, 'hb_tp'));
+                }
+                if (pre_hb_tp !== ftt.pre_hb_tp) {
+                    ftt.pre_hb_tp = pre_hb_tp;
+                    updateFttList.push({ id: ftt.id, pre_hb_tp });
+                }
+            }
+            for (const ft of ftList) {
+                const fts = _.filter(ftList, function(item) {
+                    return item.tid === ft.tid && item.id < ft.id;
+                });
+                let pre_hb_tp = 0;
+                if (fts.length > 0) {
+                    pre_hb_tp = ZhCalc.sum(_.map(fts, 'total_price'));
+                }
+                if (pre_hb_tp !== ft.pre_hb_tp) {
+                    ft.pre_hb_tp = pre_hb_tp;
+                    updateFtList.push({ id: ft.id, pre_hb_tp });
+                }
+            }
+        }
+        if (updateFtList.length > 0) {
+            const sql = 'UPDATE zh_financial_transfer SET pre_hb_tp = ? WHERE id = ?';
+            for (const ft of updateFtList) {
+                await querySql(sql, [ft.pre_hb_tp, ft.id]);
+            }
+        }
+        if (updateFttList.length > 0) {
+            const sql = 'UPDATE zh_financial_transfer_tender SET pre_hb_tp = ? WHERE id = ?';
+            for (const ftt of updateFttList) {
+                await querySql(sql, [ftt.pre_hb_tp, ftt.id]);
+            }
+        }
+    }
+};
+
+const doComplete = async function(code) {
+    try {
+        const filter = code ? ` where code = '${code}'` : '';
+        const project = await querySql('Select * From zh_project' + filter);
+        for (const p of project) {
+            console.log(`Update Project ${p.code}(${p.id}):`);
+            const subProj = await querySql('SELECT * FROM zh_sub_project where project_id = ? and is_folder = 0 and is_delete = 0;', [p.id]);
+            for (const sp of subProj) {
+                await calcPreHbTP(sp);
+            }
+            console.log('END Update;');
+        }
+    } catch (err) {
+        console.log(err);
+    }
+    BaseUtil.closePool();
+};
+const projectCode = process.argv[3];
+doComplete(projectCode);

+ 27 - 0
sql/update.sql

@@ -246,6 +246,33 @@ ADD COLUMN `multi_limit` varchar(36) NOT NULL DEFAULT '' COMMENT '联动计量
 ALTER TABLE `zh_ledger_extra_99`
 ADD COLUMN `multi_limit` varchar(36) NOT NULL DEFAULT '' COMMENT '联动计量配置' AFTER `gxby_id`;
 
+ALTER TABLE `zh_financial_transfer_tender`
+MODIFY COLUMN `sorder` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL COMMENT '期数,以逗号分隔' AFTER `tid`,
+ADD COLUMN `source` varchar(255) NULL DEFAULT '' COMMENT '资金来源' AFTER `is_lock`,
+ADD COLUMN `pre_hb_tp` decimal(30, 8) NULL DEFAULT 0 COMMENT '截止上次划拨' AFTER `hb_tp`;
+
+ALTER TABLE `zh_financial_transfer`
+ADD COLUMN `pre_hb_tp` decimal(30, 8) NULL DEFAULT 0 COMMENT '截止上次划拨金额' AFTER `total_price`;
+
+ALTER TABLE `zh_financial_pay`
+ADD COLUMN `fpsid` int NULL COMMENT '资金支付单位期id' AFTER `tid`;
+
+ALTER TABLE `zh_financial_pay_audit`
+ADD COLUMN `fpsid` int NULL COMMENT '资金支付单位期id' AFTER `fpid`;
+
+CREATE TABLE `zh_financial_pay_stage`  (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `spid` varchar(100) NULL COMMENT '项目id',
+  `company_id` int NULL DEFAULT NULL COMMENT '单位id',
+  `order` smallint(5) NULL COMMENT '期数',
+  `uid` int NULL COMMENT '申请人id',
+  `total_price` decimal(30, 8) NULL DEFAULT 0 COMMENT '本期金额',
+  `pre_tp` decimal(30, 8) NULL DEFAULT 0 COMMENT '截止上期金额',
+  `can_del` tinyint(1) NULL DEFAULT 1 COMMENT '是否可删除',
+  `create_time` datetime NULL DEFAULT NULL COMMENT '申请时间',
+  PRIMARY KEY (`id`)
+)  ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT = '资金支付单位期数表';
+
 ------------------------------------
 -- 表数据
 ------------------------------------