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

<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)">
7 months ago
<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>
7 months ago
<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>
7 months ago
<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"
7 months ago
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"
7 months ago
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>
7 months ago
</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">
7 months ago
<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>
7 months ago
</vxe-modal>
<vxe-modal
fullscreen
7 months ago
:remember="true"
class-name="oa-modal"
title="流程查看"
v-model="isShowOaModal"
7 months ago
: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,
7 months ago
fundLogEnd
} from '@/api/flow/pay'
import {
PopupManager
} from 'element-ui/lib/utils/popup'
import {
defaultModalSize
} from "@/settings";
7 months ago
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: [],
7 months ago
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)
}
}
7 months ago
})
} catch(e) {
console.warn('vxe-table reloadData failed', e)
}
})
loading.close()
} catch (err) {
console.error(err)
this.$message.error("配置失败")
loading.close()
}
},
7 months ago
// 获取历史支付明细
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 = (
7 months ago
await getFundLog({
contract_id: contractIds,
7 months ago
show_type: 1
})
)?.data || []
}
7 months ago
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
})
7 months ago
});
} catch (err) {
console.error(err)
}
},
// 获取已提交的物资数量
async getItemNum(id) {
try {
const item = await getItems({
wuzicaigou_item_id: id
7 months ago
})
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));
7 months ago
});
// 遍历合并后的 list根据 id 从 Map 中获取 total_num 并赋值给 hasPayNum
this.list.forEach(row => {
if (itemMap.has(row.id)) {
row.hasPayNum = itemMap.get(row.id)
7 months ago
}
})
}
} 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() {
7 months ago
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
7 months ago
}
if (!form.type) {
this.$message({
message: `${idToTitle[gid] || gid}】请选择款项类型`,
duration: 2000,
type: 'warning'
})
return
7 months ago
}
}
// 数量校验:按分组逐块处理,并在“等于总数量”时自动设置该块为最后一笔
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
7 months ago
}
if (totalCount > 0) {
this.$message({
message: `${idToTitle[gid] || gid}】本次报销数量大于剩余可报销数量`,
7 months ago
duration: 2000,
type: 'warning'
})
return
}
// 记录需要自动置为“最后一笔”的分组,统一在提交前弹一次提示
if (allToRemain && this.fundlogFormMap[gid] && !this.fundlogFormMap[gid].isLast) {
groupsToAutoEnd.push(gid)
}
7 months ago
}
if (groupsToAutoEnd.length > 0) {
// 等待用户确认后再继续,避免先跳转
await this.$alert('本次报销数量已等于总数量,默认为最后一笔', '提示', {
confirmButtonText: '确定'
})
groupsToAutoEnd.forEach(gid => {
if (this.fundlogFormMap[gid]) this.$set(this.fundlogFormMap[gid], 'isLast', true)
})
}
// 组合成单个审批
7 months ago
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 }
7 months ago
wuzicaigou_items.push({
flow_id: item.sourceFlowId || this.payId,
contract_id: flowIdToContractId[item.sourceFlowId] || undefined,
7 months ago
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
7 months ago
})
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
7 months ago
let payments = this.paymentsList;
const actNumsTotal = () => payments.reduce((pre, cur) => pre + (!!Number(cur.act_money) ? Number(cur.act_money) : 0), 0)
7 months ago
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)
7 months ago
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 {
7 months ago
this.list = []
this.payId = ''
this.oaUrl = ''
this.payList = []
7 months ago
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>