You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

659 lines
26 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div>
<vxe-modal :value="isShow" show-footer :z-index="zIndex" title="支付审批明细" show-zoom show-confirm-button transfer
resize :width="defaultModalSize.width" :height="defaultModalSize.height" @input="e => $emit('update:isShow',e)">
<div>
<!-- 历史支付记录查看 -->
<h3>历史支付记录查看</h3>
<vxe-table ref="tables" stripe style="margin-top: 10px" keep-source :column-config="{ resizable: true }"
:print-config="{}" :export-config="{}" :expand-config="{
accordion: true,
padding: true,
}" :row-config="{ keyField: 'id' }" :custom-config="{ mode: 'popup' }" :data="paymentsList">
<vxe-column title="项目名称" width="250" align="left" field="contract.name">
<template #default="{ row }">
{{row.contract_id ? (row.contract?row.contract.name:'') : row.away.title}}
</template>
</vxe-column>
<vxe-column title="付款申请金额(元)" width="180" align="right" field="apply_money">
<template #default="{ row }">
{{Number(row.apply_money).toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,")}}
</template>
</vxe-column>
<vxe-column title="实际支付金额(元)" width="180" align="right" field="act_money">
<template #default="{ row }">
{{Number(row.act_money).toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,")}}
</template>
</vxe-column>
<vxe-column title="款项类型" width="120" align="center" field="type"></vxe-column>
<vxe-column title="预算计划" width="330" align="left" field="act_plan_link">
<template #default="{ row }">
<template v-if="row.act_plan_link && row.act_plan_link.length > 0">
<div v-for="item in row.act_plan_link">
[{{item.plan?item.plan.year:''}}] {{(item.plan && item.plan.pid_info) ? item.plan.pid_info.name: ''}}
- {{item.plan?item.plan.name:''}}
<br />
[使用金额] {{item.use_money}}元
</div>
</template>
</template>
</vxe-column>
<vxe-column title="审核状态" width="120" align="center" field="status">
<template #default="{ row }">
<div v-if="row.status===0">待审核</div>
<div v-else>已审核</div>
</template>
</vxe-column>
<vxe-column title="流程状态" width="120" align="center" field="fund_log_flow_links">
<template #default="{ row }">
<template v-for="item in row.fund_log_flow_links">
<div v-if="item.tag === 'pay'">
{{flowStatus[item.flow_status]}}
</div>
</template>
</template>
</vxe-column>
<vxe-column title="次数" width="80" align="center" field="pay_count">
<template #default="{ row }">
{{row.contract_id ? row.pay_count : row.pay_count_away}}
</template>
</vxe-column>
<vxe-column title="是否为最后一笔" width="120" align="center" field="is_end">
<template #default="{ row }">
<div v-if="row.is_end===1">是</div>
<div v-else>否</div>
</template>
</vxe-column>
<vxe-column title="经办人" width="120" align="center" field="admin.name">
<template #default="{ row }">
{{row.admin?row.admin.name:''}}
</template>
</vxe-column>
<vxe-column title="业务科室" width="160" align="center" field="department.name">
<template #default="{ row }">
{{row.department?row.department.name:''}}
</template>
</vxe-column>
<vxe-column title="备注" width="460" align="left" field="remark">
</vxe-column>
<vxe-column title="创建信息" width="180" align="center" field="created_at">
</vxe-column>
<vxe-column
min-width="120"
header-align="center"
field="operate"
title="操作"
fixed="right"
>
<template #default="{ row }">
<el-button type="primary" size="small" @click="showDetail(row)"
>查看</el-button>
</template>
</vxe-column>
</vxe-table>
<div style="display: flex;justify-content: center;margin-top:15px">
<el-button :disabled="paymentsList.length>0?false:true" type="primary" size="small" @click="payClose">设为已支付完毕 <i class="el-icon-arrow-right"></i> </el-button>
</div>
<h3>发起新的支付审批</h3>
<div v-for="pay in payList" :key="pay.id" style="margin-top: 12px;">
<div style="font-weight: 600; margin: 6px 0;">{{ pay.title }}</div>
<div style="display: flex;align-items: center;justify-content: space-between;">
<div style="display: flex;align-items: center;">
<div class="xy-table-item-label">
<span style="color: red; font-weight: 600; padding-right: 4px">*</span>申请付款金额
</div>
<div class="xy-table-item-content xy-table-item-price">
<el-input clearable type="number" placeholder="请填写付款金额" v-model="fundlogFormMap[pay.id].applyMoney"
style="width: 150px;margin-left:10px" />
</div>
</div>
<div style="display: flex;align-items: center;">
<div class="xy-table-item-label">
<span style="color: red; font-weight: 600; padding-right: 4px">*</span>款项类型
</div>
<div style="display: flex;align-items: center;">
<el-select placeholder="选择款项类型或直接录入其他类型" v-model="fundlogFormMap[pay.id].type" style="width: 200px;margin-left:10px"
filterable allow-create clearable popper-append-to-body>
<el-option v-for="item in paymentType" :key="item" :label="item" :value="item">
</el-option>
</el-select>
</div>
</div>
<div style="display: flex;align-items: center;">
<div class="xy-table-item-label">
<span style="color: red; font-weight: 600; padding-right: 4px">*</span>是否为最后一笔
</div>
<div class="xy-table-item-content" style="margin-left:10px">
<el-switch v-model="fundlogFormMap[pay.id].isLast" />
</div>
</div>
</div>
<vxe-table :ref="'table_' + pay.id" stripe style="margin-top: 6px" keep-source :column-config="{ resizable: true }"
:print-config="{}" :export-config="{}" :expand-config="{
accordion: true,
padding: true,
}" :checkbox-config="{
reserve: true,
highlight: true,
range: true,
checkMethod: ({ row }) => {
return parseFloat(row.hasPayNum) < parseFloat(row.num)
}
}" :row-config="{ keyField: 'id' }" :custom-config="{ mode: 'popup' }" :data="getGroupList(pay.id)">
<vxe-column type="checkbox" width="50" align="center"></vxe-column>
<vxe-column title="本次报销数量" width="120" align="center" field="payNum">
<template #default="{ row }">
<vxe-number-input v-if="parseFloat(row.hasPayNum) < parseFloat(row.num)" v-model="row.payNum"
placeholder="数量"></vxe-number-input>
<div v-else>-</div>
</template>
</vxe-column>
<vxe-column title="已报销数量" width="100" align="center" field="hasPayNum">
</vxe-column>
<vxe-column align="center" v-for="item in table_item" :key="item.prop + '_' + pay.id" :title="item.label" :field="item.prop"></vxe-column>
</vxe-table>
</div>
</div>
<template #footer>
<div style="margin-top: 20px;display: flex;justify-content: center;">
<el-button type="primary" size="small" @click="submit">发起支付审批 <i class="el-icon-arrow-right"></i> </el-button>
</div>
</template>
</vxe-modal>
<vxe-modal
fullscreen
:remember="true"
class-name="oa-modal"
title="流程查看"
v-model="isShowOaModal"
:z-index="zIndex+100"
>
<div style="width: 100%;height: 100%;">
<iframe style="width: 100%;height: 100%;border-radius: 0 0 6px 6px;" :src="oaUrl" frameborder="0"></iframe>
</div>
</vxe-modal>
</div>
</template>
<script>
import {
view,
fieldConfig
} from '@/api/flow'
import {
getItems,
detailContract,
getFundLog,
fundLogEnd
} from '@/api/flow/pay'
import {
PopupManager
} from 'element-ui/lib/utils/popup'
import {
defaultModalSize
} from "@/settings";
import {getToken} from "@/utils/auth";
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true
},
// 新增:接收支付列表,包含各项的 out_contract_id
// payList: {
// type: Array,
// default: () => []
// }
},
data() {
return {
payId: '',
payList:[],
defaultModalSize,
zIndex: PopupManager.nextZIndex(),
list: [],
// 每个区块pay.id对应的表单申请金额/类型/是否最后一笔
fundlogFormMap: {},
table_item: [],
flowDetail: {},
fundlogForm: {
applyMoney: 0,
type: '',
isLast: false,
},
paymentType: ["进度款", "结算款", "质保金"],
oaUrl: '',
isShowOaModal: false,
paymentsList: [],
flowStatus: {
'-1': '已退回',
'0': '办理中',
'1': '已完成'
}
}
},
methods: {
// 获取某一 pay 的分组数据
getGroupList(flowId) {
return (this.list || []).filter(r => r.sourceFlowId === flowId)
},
async getConfig() {
// 改造:不再通过 view 接口获取,直接使用传入的 payList
const loading = this.$loading({
lock: true,
text: "拼命加载中",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.8)",
});
try {
console.log('this.payList', this.payList)
// 初始化本地可编辑列表(聚合 payList 的各自明细JSON.parse(data.caigoumingxi)
this.list = [];
(this.payList || []).forEach(pay => {
let subList = []
try {
subList = JSON.parse(pay?.data?.caigoumingxi || '[]')
} catch(e) {
subList = []
}
// 初始化每个区块的表单默认值
if (!this.fundlogFormMap[pay.id]) {
this.$set(this.fundlogFormMap, pay.id, {
applyMoney: 0,
type: '',
isLast: false
})
}
subList.forEach(item => {
const newItem = Object.assign({}, item, {
payNum: 0,
hasPayNum: 0,
sourceFlowId: pay.id
})
this.list.push(newItem)
})
})
console.log('this.list', this.list)
// 组装逗号分隔的明细 id 字符串用于 getItemNum从 payList 的 data.caigoumingxi 中提取)
const ids = []
;(this.payList || []).forEach(pay => {
try {
const subList = JSON.parse(pay?.data?.caigoumingxi || '[]')
subList.forEach(item => {
if (item && item.id) ids.push(item.id)
})
} catch(e) {
// 忽略解析错误
}
})
const idsStr = ids.join(',')
if (idsStr) {
await this.getItemNum(idsStr)
}
// 获取字段配置:取第一条的 sub_custom_model_id
// const firstSubModelId = this.payList && this.payList[0] && (this.payList[0].sub_custom_model_id || this.payList[0].field?.sub_custom_model_id)
// if (firstSubModelId) {
await this.getFieldConfig(81)
// }
// 历史支付记录由 payList 汇总的 out_contract_id 直接获取
await this.getFundLogList()
// 确保子表渲染到最新数据
this.$nextTick(() => {
try {
(this.payList || []).forEach(pay => {
const refKey = 'table_' + pay.id
const tableRef = this.$refs[refKey]
const data = this.getGroupList(pay.id)
if (tableRef && data) {
if (typeof tableRef.reloadData === 'function') {
tableRef.reloadData(data)
} else if (typeof tableRef.loadData === 'function') {
tableRef.loadData(data)
}
}
})
} catch(e) {
console.warn('vxe-table reloadData failed', e)
}
})
loading.close()
} catch (err) {
console.error(err)
this.$message.error("配置失败")
loading.close()
}
},
// 获取历史支付明细
async getFundLogList() {
// 从传入的 payList 中汇总 out_contract_id用逗号连接
const contractIds = (this.payList || [])
.map(i => i && i.out_contract_id)
.filter(id => !!id)
.join(',');
let payments = []
if (contractIds) {
payments = (
await getFundLog({
contract_id: contractIds,
show_type: 1
})
)?.data || []
}
this.paymentsList = payments
console.log("paymentsList", this.paymentsList)
},
// 获取采购明细
async getFieldConfig(id) {
try {
const res = await fieldConfig(id)
console.log("res", res)
const fields = res?.customModel?.fields
fields.forEach((field) => {
this.table_item.push({
label: field.label,
prop: field.name
})
});
} catch (err) {
console.error(err)
}
},
// 获取已提交的物资数量
async getItemNum(id) {
try {
const item = await getItems({
wuzicaigou_item_id: id
})
console.log("item", item)
if (item.length > 0) {
const itemMap = new Map();
item.forEach(item => {
itemMap.set(item.wuzicaigou_item_id, parseFloat(item.total_num));
});
// 遍历合并后的 list根据 id 从 Map 中获取 total_num 并赋值给 hasPayNum
this.list.forEach(row => {
if (itemMap.has(row.id)) {
row.hasPayNum = itemMap.get(row.id)
}
})
}
} catch (err) {
console.error(err)
}
},
showDetail(row){
let url = `/oa/#/flow/detail?module_name=oa&auth_token=${window.encodeURIComponent(getToken())}&isSinglePage=1&flow_id=`
let pay = row.fund_log_flow_links.find(i => i.tag === 'pay')
//url += `&to=/flow/detail?flow_id=${caigou.id}`
url += pay?.flow_id
this.oaUrl = url
this.isShowOaModal = true
},
payClose(){
this.$confirm('将此采购记录设为“已⽀付完毕”,同时会将最后⼀笔⽀付记录的“是否为最后⼀笔”属性设置为“是”,请确认是否要进⾏此操作', '提示', {
confirmButtonText: '确定',
callback: action => {
if(action==='confirm'){
fundLogEnd({
contract_id:this.flowDetail.out_contract_id,
}).then(res=>{
this.$emit('update:isShow', false)
this.$emit('refresh')
})
}
// this.fundlogForm.isLast = true
}
});
},
async submit() {
try {
// 收集所有子表勾选记录
let records = []
this.payList.forEach(pay => {
const refKey = 'table_' + pay.id
let tableRef = this.$refs[refKey]
// 兼容 v-for 下 ref 可能为数组的情况
if (Array.isArray(tableRef)) tableRef = tableRef[0]
if (tableRef) {
const sel = typeof tableRef.getCheckboxRecords === 'function' ? (tableRef.getCheckboxRecords() || []) : []
const reserveSel = typeof tableRef.getCheckboxReserveRecords === 'function' ? (tableRef.getCheckboxReserveRecords() || []) : []
records = records.concat(sel, reserveSel)
}
})
console.log("records", records)
// 分组逐块校验数量
if (records.length < 1) {
this.$message({
message: '请选择需要报销的明细',
duration: 2000,
type: 'warning'
})
return
}
// 校验每个分组pay.id的区块表单是否填写金额、类型
const idToTitle = {}
;(this.payList || []).forEach(p => { idToTitle[p.id] = p.title })
const groupIds = Array.from(new Set(records.map(r => r.sourceFlowId)))
for (const gid of groupIds) {
const form = this.fundlogFormMap[gid] || {}
if (!form.applyMoney || Number(form.applyMoney) <= 0) {
this.$message({
message: `【${idToTitle[gid] || gid}】请填写本次申请付款金额`,
duration: 2000,
type: 'warning'
})
return
}
if (!form.type) {
this.$message({
message: `【${idToTitle[gid] || gid}】请选择款项类型`,
duration: 2000,
type: 'warning'
})
return
}
}
// 数量校验:按分组逐块处理,并在“等于总数量”时自动设置该块为最后一笔
const groupMap = records.reduce((acc, row) => {
const gid = row.sourceFlowId
if (!acc[gid]) acc[gid] = []
acc[gid].push(row)
return acc
}, {})
const groupsToAutoEnd = []
for (const gid of Object.keys(groupMap)) {
const rows = groupMap[gid]
let count = 0
let totalCount = 0
let allToRemain = true
rows.forEach(item => {
const payNum = Number(item.payNum)
const num = Number(item.num)
const hasPay = Number(item.hasPayNum)
const remain = num - hasPay
if (isNaN(payNum) || payNum === 0 || payNum > num) count++
if (payNum + hasPay > num) totalCount++
// 判断是否每一条都“正好补齐剩余”
if (!(payNum > 0 && Math.abs(payNum - remain) < 1e-6)) {
allToRemain = false
}
})
if (count > 0) {
this.$message({
message: `【${idToTitle[gid] || gid}】报销数量不能为0或报销数量大于总数量`,
duration: 2000,
type: 'warning'
})
return
}
if (totalCount > 0) {
this.$message({
message: `【${idToTitle[gid] || gid}】本次报销数量大于剩余可报销数量`,
duration: 2000,
type: 'warning'
})
return
}
// 记录需要自动置为“最后一笔”的分组,统一在提交前弹一次提示
if (allToRemain && this.fundlogFormMap[gid] && !this.fundlogFormMap[gid].isLast) {
groupsToAutoEnd.push(gid)
}
}
if (groupsToAutoEnd.length > 0) {
// 等待用户确认后再继续,避免先跳转
await this.$alert('本次报销数量已等于总数量,默认为最后一笔', '提示', {
confirmButtonText: '确定'
})
groupsToAutoEnd.forEach(gid => {
if (this.fundlogFormMap[gid]) this.$set(this.fundlogFormMap[gid], 'isLast', true)
})
}
// 组合成单个审批
let wuzicaigou_items = []
let zhifutitle = ''
// 建立 flowId -> contractId 映射
const flowIdToContractId = {}
;(this.payList || []).forEach(p => {
const fid = p.id
if (fid) flowIdToContractId[fid] = p.out_contract_id
})
records.forEach(item => {
const form = this.fundlogFormMap[item.sourceFlowId] || { applyMoney: 0, type: '', isLast: false }
wuzicaigou_items.push({
flow_id: item.sourceFlowId || this.payId,
contract_id: flowIdToContractId[item.sourceFlowId] || undefined,
wuzicaigou_id: item.belongs_id,
wuzicaigou_item_id: item.id,
name: item.pinminghuofuwuxuqiu,
num: item.payNum,
total: item.num,
// 区块级的额外字段注入到每条明细,便于后端识别
apply_money: form.applyMoney,
type: form.type,
is_end: form.isLast ? 1 : 0
})
zhifutitle += item.pinminghuofuwuxuqiu + '*' + item.payNum + ','
})
console.log("wuzicaigou_items",wuzicaigou_items)
// return
// 聚合相关合同信息:从 payList 收集 contract_id逐个请求 detailContract 并整合为数组
const contractIds = Array.from(new Set((this.payList || []).map(p => p.out_contract_id).filter(Boolean)))
let contracts = []
if (contractIds.length > 0) {
try {
contracts = await Promise.all(contractIds.map(id => detailContract({ id })))
} catch (e) {
console.warn('fetch contracts failed', e)
}
}
// 仍以第一份合同作为展示主合同(标题等),其余信息通过聚合参与计算
const mainContract = contracts && contracts.length > 0 ? contracts[0] : null
let payments = this.paymentsList;
const actNumsTotal = () => payments.reduce((pre, cur) => pre + (!!Number(cur.act_money) ? Number(cur.act_money) : 0), 0)
let zhifutitle2 = zhifutitle.slice(0, -1)
if (mainContract?.name) zhifutitle += `${mainContract.name} 等${contractIds.length}个合同)`
// 从明细中聚合主数据合同ID串与金额合计
const contractIdStr = Array.from(new Set(wuzicaigou_items.map(i => i.contract_id).filter(Boolean))).join(',')
const amtTotal = wuzicaigou_items.reduce((sum, i) => sum + (Number(i.group_apply_money || i.apply_money) || 0), 0)
// 从合同列表聚合展示数据
const sumMoney = (contracts || []).reduce((sum, c) => sum + (Number(c?.money) || 0), 0)
const sumPlanLen = (contracts || []).reduce((sum, c) => sum + ((c?.sign_plan?.length) || 0), 0)
const plansNames = Array.from(new Set((contracts || []).flatMap(c => (c?.plans || []).map(p => p?.name)).filter(Boolean))).join(',')
const contractNos = (contracts || []).map(c => c?.number).filter(Boolean).join(',')
const relatedFlows = Array.from(new Set((contracts || []).flatMap(c => (c?.contract_flow_links || []).map(l => l?.flow_id)).filter(Boolean))).join(',')
const baseInfo = {
// oaUrl: mainContract?.number,
zhifutitle,
contract_id: contractIdStr,
type: '',
is_end: '',
amt: amtTotal,
xiangxishuoming: zhifutitle,
yifujine: actNumsTotal(),
cishu: payments.length,
xiangmuzonge: sumMoney,
zhifucishu: sumPlanLen,
liezhiqudao: plansNames,
contractno: contractNos,
guanlianliucheng: relatedFlows,
// xiangmuzonge: mainContract?.money,
// zhifucishu: mainContract?.sign_plan?.length,
// liezhiqudao: mainContract?.plans.map(i => i.name)?.toString(),
// contractno: mainContract?.number,
// guanlianliucheng: mainContract?.contract_flow_links?.map(i => i.flow_id)?.toString(),
wuzicaigou_items
}
const url = `/flow/create?&module_name=oa&isSinglePage=1&module_id=75&default_json=${window.encodeURIComponent(JSON.stringify(baseInfo))}`
this.$router.push(url)
this.$emit('update:isShow', false)
} catch (err) {
console.error(err)
}
}
},
computed: {},
watch: {
isShow(newVal) {
if (newVal) {
this.zIndex = PopupManager.nextZIndex()
// 直接拉取历史支付记录(无需依赖 getConfig
console.log("this.payList123", this.payList)
this.getFundLogList()
// 加载表格字段配置与数量统计(基于传入的 payList
this.getConfig()
} else {
this.list = []
this.payId = ''
this.oaUrl = ''
this.payList = []
this.isShowOaModal = false
this.table_item = []
this.paymentsList = []
this.flowDetail = {}
this.fundlogForm = {
applyMoney: 0,
type: '',
isLast: false,
}
this.$refs.table.clearCheckboxRow()
}
}
}
}
</script>
<style scoped lang="scss">
::v-deep .el-checkbox-group {
font-size: inherit;
}
</style>