<template>
|
<el-dialog v-model="visible" title="账单认领" width="1150px" destroy-on-close :close-on-click-modal="false">
|
<div style="text-align: right;margin-bottom: 10px;">
|
<el-button type="warning" v-if="isViewMode" plain icon="Download" @click="handleExport"
|
v-hasPermi="['cwgl:fundFlowClaimDetail:export']">导出
|
</el-button>
|
<!-- <el-button type="primary" @click="handleFinalSubmit">确 定</el-button> -->
|
</div>
|
<div class="claim-wrapper">
|
<!-- <div class="section-header">流水详细信息</div> -->
|
<el-descriptions :column="3" border class="mb-20">
|
<el-descriptions-item label="id">{{ detail.id }}</el-descriptions-item>
|
<el-descriptions-item label="银行流水号">{{ detail.bankFlowNo }}</el-descriptions-item>
|
<el-descriptions-item label="单位">{{ detail.company }}</el-descriptions-item>
|
|
<el-descriptions-item label="本方账号">{{ detail.ourAccount }}</el-descriptions-item>
|
<el-descriptions-item label="本方账户开户行">{{ detail.ourBankName }}</el-descriptions-item>
|
<el-descriptions-item label="收支标识">
|
{{ dictFormat(sys_income_expenses, detail.incomeExpenseFlag) }}
|
</el-descriptions-item>
|
|
<el-descriptions-item label="交易金额">
|
<span class="amount-text">{{ detail.transactionAmount }}</span>
|
</el-descriptions-item>
|
<el-descriptions-item label="交易币种">{{ detail.currency }}</el-descriptions-item>
|
<el-descriptions-item label="对方账号">{{ detail.counterpartyAccount }}</el-descriptions-item>
|
|
<el-descriptions-item label="对方户名">{{ detail.counterpartyName }}</el-descriptions-item>
|
<el-descriptions-item label="交易日期">{{ detail.transactionDate }}</el-descriptions-item>
|
<el-descriptions-item label="用途">{{ detail.purpose }}</el-descriptions-item>
|
|
<el-descriptions-item label="摘要" :span="1">{{ detail.summary }}</el-descriptions-item>
|
<el-descriptions-item label="附言" :span="1">{{ detail.remarks }}</el-descriptions-item>
|
<el-descriptions-item label="已认领金额">{{ detail.claimedAmount || 0 }}</el-descriptions-item>
|
|
<el-descriptions-item label="待认领金额">
|
<span class="text-danger font-bold">{{ remainingAmountDr }}</span>
|
</el-descriptions-item v-if="isViewMode">
|
<el-descriptions-item label="关联账单类型">
|
<span v-if="detail.incomeExpenseFlag == 0">
|
客户
|
</span>
|
<span v-if="detail.incomeExpenseFlag == 1">
|
供应商
|
</span>
|
</el-descriptions-item>
|
<el-descriptions-item label="" :span="1"></el-descriptions-item>
|
</el-descriptions>
|
|
<div class="section-header">{{ isViewMode ? '账单认领明细' : '账单认领' }}</div>
|
|
<div v-if="!isViewMode" class="type-selection-bar mb-20">
|
<span class="required-label">关联账单类型</span>
|
<el-radio-group v-model="detail.incomeExpenseFlag" disabled @change="handleTypeChange">
|
<el-radio :label="0">应收账单</el-radio>
|
<el-radio :label="1">应付账单</el-radio>
|
</el-radio-group>
|
</div>
|
|
<div v-if="!isViewMode" class="mb-10">
|
<el-button type="primary" icon="Plus" @click="handleAddRow">新增</el-button>
|
</div>
|
|
<el-table :data="detail.claimDetails" border stripe>
|
<el-table-column label="账单编号" min-width="220">
|
<template #default="{ row, $index }">
|
<el-input v-model="row.billNo" @click="openReceivableDialog($index)" :disabled="!row.$edit" readonly
|
placeholder="点击选择账单">
|
<template v-if="row.$edit" #append>
|
<el-button icon="Search" @click="openReceivableDialog($index)" :disabled="!row.$edit" />
|
</template>
|
</el-input>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="关联企业类型" width="120">
|
<template #default="{ row }">
|
<span>{{ row.relatedCompanyType }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="关联企业名称" width="150">
|
<template #default="{ row }">
|
<span>{{ row.relatedCompanyName }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="账单金额" width="120">
|
<template #default="{ row }">
|
<span>{{ row.billAmount || 0 }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="账单待结算金额" width="130">
|
<template #default="{ row }">
|
<span>{{ row.billPendingAmount || 0 }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="认领金额" width="150">
|
<template #default="{ row }">
|
<el-input-number v-model="row.claimAmount" :precision="2" :controls="false" :disabled="!row.$edit" :min="0"
|
style="width: 100%" placeholder="输入金额" @change="(val) => handleAmountChange(val, row)" />
|
</template>
|
</el-table-column>
|
|
<el-table-column label="认领日期" width="200"> <template #default="{ row }">
|
<el-date-picker v-model="row.claimDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
|
:disabled="!row.$edit" style="width: 100%" placeholder="选择日期时间" />
|
</template>
|
</el-table-column>
|
|
<el-table-column label="备注">
|
<template #default="{ row }">
|
<el-input v-model="row.remark" :disabled="!row.$edit" placeholder="备注信息" />
|
</template>
|
</el-table-column>
|
|
<el-table-column label="操作" v-if="!isViewMode" width="120" fixed="right" align="center">
|
<template #default="{ row, $index }">
|
<template v-if="row.$edit">
|
<el-button type="primary" link @click="handleSaveRow(row)">确定</el-button>
|
</template>
|
<template v-else>
|
<!-- <el-button type="primary" link @click="row.$edit = true">修改</el-button> -->
|
<el-button type="danger" link @click="handleDeleteRow(row)">删除</el-button>
|
</template>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<pagination v-show="total > 10" :total="total" v-model:page="queryParams.pageNum"
|
v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
</div>
|
|
<template #footer>
|
<div style="text-align: center;">
|
<el-button @click="handleCancel">关 闭</el-button>
|
<!-- <el-button type="primary" @click="handleFinalSubmit">确 定</el-button> -->
|
</div>
|
</template>
|
</el-dialog>
|
|
<receivableBillManagementDialog :default-status="-1" v-model:visible="receivablIshow"
|
:default-selected-id="detail.invoiceManageId" @confirm="receivablForm" />
|
|
<AccountsPayableManagementDialog :default-status="-1" v-model:visible="accountsIshow"
|
:default-selected-id="detail.invoiceManageId" @confirm="accountsForm" />
|
</template>
|
|
<script setup lang="ts">
|
import { ref, computed } from 'vue';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
import useCurrentInstance from "@/utils/useCurrentInstance";
|
import receivableBillManagementDialog from "../receivableBillManagementDialog/index.vue";
|
import AccountsPayableManagementDialog from "../AccountsPayableManagementDialog/index.vue";
|
import { addFundFlowClaimDetailClaim } from "@/api/cwgl/fundFlow";
|
import { delFundFlowClaimDetail, } from "@/api/cwgl/fundFlowClaimDetail";
|
import { listFundFlowClaimDetail } from "@/api/cwgl/fundFlowClaimDetail";
|
import { getFundFlow, } from "@/api/cwgl/fundFlow";
|
|
const { proxy } = useCurrentInstance();
|
const { sys_income_expenses } = proxy.useDict('sys_income_expenses');
|
|
const dictFormat = (dict: any, value: any) => proxy.selectDictLabel(dict, value);
|
const emit = defineEmits(['submit']);
|
const visible = ref(false);
|
const detail = ref<any>({
|
claimDetails: []
|
});
|
const billType = ref('');
|
|
// 1. 【待认领金额计算】: 交易金额 - 已认领金额 )
|
const remainingAmountDr = computed(() => {
|
const total = parseFloat(detail.value.transactionAmount) || 0;
|
const alreadyClaimed = parseFloat(detail.value.claimedAmount) || 0;
|
const res = total - alreadyClaimed;
|
return res.toFixed(2);
|
});
|
|
// 1. 实时计算待认领池子(剩余总额)
|
const remainingAmount = computed(() => {
|
const total = parseFloat(detail.value.transactionAmount) || 0; // 流水总额
|
const historicClaimed = parseFloat(detail.value.claimedAmount) || 0; // 历史已认领
|
|
// 累加当前表格中所有行填写的金额
|
const currentTableTotal = (detail.value.claimDetails || []).reduce((sum, item) => {
|
return sum + (parseFloat(item.claimAmount) || 0);
|
}, 0);
|
|
const res = total - historicClaimed - currentTableTotal;
|
return res.toFixed(2);
|
});
|
|
// 修正后的函数
|
const handleAmountChange = (val: number | null, row: any) => {
|
if (val === null) return;
|
|
// 1. 获取池子剩余(需要加回当前行金额)
|
const currentRemaining = parseFloat(remainingAmount.value) || 0;
|
const currentLineAmount = val || 0;
|
// 计算此时如果没填这一行,池子有多少钱
|
// 注意:这里因为 val 已经改变了,remainingAmount 已经减去了新的 val
|
// 所以池子可用总量 = remainingAmount + val
|
const totalAvailablePool = currentRemaining + currentLineAmount;
|
|
// 2. 确定两个上限
|
const limitByBill = row.billAmount || 0; // 账单上限
|
const limitByPool = totalAvailablePool; // 流水上限
|
|
// 3. 校验并修正
|
if (val > limitByBill) {
|
ElMessage.warning(`认领金额不能超过账单金额 (${limitByBill})`);
|
row.claimAmount = limitByBill;
|
} else if (val > limitByPool) {
|
ElMessage.warning(`认领金额不能超过流水待认领金额 (${limitByPool.toFixed(2)})`);
|
// row.claimAmount = parseFloat(limitByPool.toFixed(2));
|
}
|
};
|
|
// 建议保留 getMaxClaimAmount 作为一个保险上限,或者直接删除它
|
const getMaxClaimAmount = (row: any) => {
|
// 为了不干扰输入过程,这里返回一个极大的安全值,或者流水总金额
|
return parseFloat(detail.value.transactionAmount) || 99999999;
|
};
|
// 重置表单和数据
|
const resetForm = () => {
|
// 1. 清空明细表格
|
detail.value = {
|
claimDetails: [],
|
// 如果有其他需要清空的流水基础信息,也可以在这里初始化
|
transactionAmount: 0,
|
claimedAmount: 0
|
};
|
|
// 2. 清空选择的账单类型
|
billType.value = '';
|
|
// 3. 重置索引和弹窗控制变量
|
currentRowIndex.value = null;
|
receivablIshow.value = false;
|
accountsIshow.value = false;
|
};
|
// 取消按钮点击事件
|
const handleCancel = () => {
|
visible.value = false;
|
resetForm();
|
};
|
const isViewMode = ref(false); // 新增:模式控制
|
|
// --- 新增:分页与搜索相关的响应式变量 ---
|
const total = ref(0);
|
const loading = ref(false);
|
const queryParams = ref({
|
pageNum: 1,
|
pageSize: 10,
|
fundFlowId: null as any // 关联的流水ID
|
});
|
|
// --- 新增:获取列表数据的方法 ---
|
const getList = async () => {
|
if (!detail.value.id) return;
|
|
loading.value = true;
|
try {
|
const res = await listFundFlowClaimDetail({
|
...queryParams.value,
|
fundFlowId: detail.value.id
|
});
|
if (res.code === 200) {
|
detail.value.claimDetails = res.rows;
|
detail.value.claimDetails.forEach((item: any) => {
|
if (item.$edit === undefined) {
|
item.$edit = false;
|
}
|
});
|
total.value = res.total;
|
}
|
} catch (error) {
|
console.error("获取明细列表失败", error);
|
} finally {
|
loading.value = false;
|
}
|
};
|
const handleExport = () => {
|
proxy.download("/cwgl/fundFlowClaimDetail/export", { ...queryParams.value })
|
}
|
|
// 打开弹窗
|
const open = (rowData: any, mode: 'view' | 'edit' = 'edit') => {
|
// 1. 先重置一次,防止上次残留
|
resetForm();
|
isViewMode.value = mode === 'view'; // 设置模式
|
// 2. 浅拷贝基础数据
|
detail.value = {
|
...rowData,
|
};
|
|
// 3. 处理 billType 的自动回显逻辑
|
if (detail.value.claimDetails && detail.value.claimDetails.length > 0) {
|
// 取出第一条明细的关联企业类型
|
const firstCompanyType = detail.value.claimDetails[0].relatedCompanyType;
|
|
// 确保已有的数据行不会变成编辑模式
|
getList()
|
}
|
|
visible.value = true;
|
};
|
// 关联账单类型切换逻辑
|
const handleTypeChange = (val: string) => {
|
if (detail.value.claimDetails && detail.value.claimDetails.length > 0) {
|
ElMessageBox.confirm('切换账单类型将清空当前已添加的认领明细,是否继续?', '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}).then(() => {
|
detail.value.claimDetails = [];
|
ElMessage.success('已清空明细');
|
}).catch(() => {
|
billType.value = val === 'RECEIVABLE' ? 'PAYABLE' : 'RECEIVABLE';
|
});
|
}
|
};
|
|
// 新增行
|
// 修改新增行逻辑
|
const handleAddRow = () => {
|
|
const defaultCompanyType = detail.value.incomeExpenseFlag == 0 ? '客户' : '供应商';
|
|
// 获取当前时间的 YYYY-MM-DD HH:mm:ss 格式
|
const now = new Date();
|
const formatTime = (date: Date) => {
|
const pad = (num: number) => String(num).padStart(2, '0');
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
|
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
};
|
|
detail.value.claimDetails.push({
|
billNo: '',
|
claimAmount: 0,
|
claimDate: formatTime(now), // 使用带时分秒的默认值
|
remark: '',
|
relatedCompanyType: defaultCompanyType,
|
relatedCompanyName: '',
|
billAmount: 0,
|
billPendingAmount: 0,
|
$edit: true
|
});
|
};
|
|
// 保存行
|
const handleSaveRow = (row: any) => {
|
if (!row.billNo) return ElMessage.warning('请选择账单编号');
|
|
// 转换数值,确保计算准确
|
const currentClaim = parseFloat(row.claimAmount) || 0;
|
if (currentClaim <= 0) return ElMessage.warning('认领金额必须大于0');
|
|
// 1. 获取当前计算属性中的剩余额度(此时已经扣除了所有行的 claimAmount)
|
const currentRemaining = parseFloat(remainingAmount.value) || 0;
|
|
// 2. 【关键】计算该行实际可用的“剩余池子上限”
|
// 公式:池子真实余量 = 当前显示的余额 + 该行刚才占用的额度
|
const poolLimit = currentRemaining + currentClaim;
|
|
// 3. 获取账单本身的上限
|
const billLimit = row.billPendingAmount || row.billAmount || 0;
|
|
// --- 开始拦截判定 ---
|
|
// 判定 A:超过账单欠款
|
if (currentClaim > billLimit) {
|
return ElMessage.error(`保存失败:认领金额不能超过账单待结算金额 (${billLimit})`);
|
}
|
|
// 判定 B:超过流水总余量
|
// 使用 0.01 容差防止 JS 浮点数计算误差
|
if (currentClaim > (poolLimit + 0.01)) {
|
return ElMessage.error(`保存失败:认领总额超过流水待认领金额,该行当前最大可填 ${poolLimit.toFixed(2)}`);
|
}
|
addFundFlowClaimDetailClaim(row, detail.value.id).then((response) => {
|
if (response.code == 200) {
|
proxy.$modal.msgSuccess("保存成功");
|
getFundFlow(detail.value.id).then((res) => {
|
if (res.code == 200) {
|
detail.value = res.data;
|
getList(); // 使用统一的 getList 方法
|
|
}
|
});
|
}
|
})
|
};
|
|
const handleDeleteRow = (row: any) => {
|
proxy.$modal.confirm('是否确认删除账单编号为"' + row.billNo + '"?').then(function () {
|
return delFundFlowClaimDetail(row.id);
|
}).then((res) => {
|
if (res.code == 200) {
|
getFundFlow(detail.value.id).then((res) => {
|
if (res.code == 200) {
|
detail.value = res.data;
|
getList(); // 使用统一的 getList 方法
|
proxy.$modal.msgSuccess("删除成功");
|
}
|
});
|
}
|
|
|
}).catch(() => { });
|
};
|
|
const receivablIshow = ref(false);
|
const currentRowIndex = ref<number | null>(null);
|
const accountsIshow = ref(false);
|
|
const openReceivableDialog = (index: number) => {
|
currentRowIndex.value = index;
|
if (detail.value.incomeExpenseFlag == '0') {
|
receivablIshow.value = true;
|
} else if (detail.value.incomeExpenseFlag == '1') {
|
accountsIshow.value = true;
|
}
|
|
};
|
|
// 回填弹窗选中的数据
|
const receivablForm = (data: any) => {
|
const defaultCompanyType = detail.value.incomeExpenseFlag == 0 ? '客户' : '供应商';
|
|
if (currentRowIndex.value !== null && data) {
|
const row = detail.value.claimDetails[currentRowIndex.value];
|
|
row.billNo = data.systemNo
|
row.relatedCompanyType = defaultCompanyType;
|
row.relatedCompanyName = data.customerName
|
row.billAmount = data.totalAmount || 0;
|
row.billPendingAmount = data.pendingAmount || 0;
|
|
|
currentRowIndex.value = null;
|
receivablIshow.value = false;
|
}
|
};
|
|
const accountsForm = (data: any) => {
|
const defaultCompanyType = detail.value.incomeExpenseFlag == 0 ? '客户' : '供应商';
|
if (currentRowIndex.value !== null && data) {
|
const row = detail.value.claimDetails[currentRowIndex.value];
|
row.billNo = data.systemNo
|
row.relatedCompanyType = defaultCompanyType;
|
row.relatedCompanyName = data.supplierName
|
row.billAmount = data.totalAmount || 0;
|
row.billPendingAmount = data.pendingAmount || 0;
|
currentRowIndex.value = null;
|
accountsIshow.value = false;
|
}
|
};
|
|
// 确定提交
|
const handleFinalSubmit = () => {
|
if (!billType.value) return ElMessage.warning('请选择关联账单类型');
|
if (detail.value.claimDetails.length === 0) return ElMessage.warning('请添加至少一条认领明细');
|
|
const hasEditing = detail.value.claimDetails.some((row: any) => row.$edit);
|
if (hasEditing) return ElMessage.warning('请先保存正在编辑的明细行');
|
|
if (parseFloat(remainingAmount.value) < 0) {
|
return ElMessage.error('最终认领总额不能超过流水待认领金额');
|
}
|
emit('submit', detail.value);
|
// console.log("提交数据:", detail.value);
|
// ElMessage.success('操作成功');
|
// visible.value = false;
|
};
|
|
defineExpose({ open, handleCancel });
|
</script>
|
|
<style scoped>
|
.claim-wrapper {
|
padding: 0 10px;
|
}
|
|
.section-header {
|
font-size: 16px;
|
font-weight: bold;
|
margin: 15px 0 10px;
|
border-left: 4px solid #409eff;
|
padding-left: 10px;
|
color: #303133;
|
}
|
|
.type-selection-bar {
|
display: flex;
|
align-items: center;
|
gap: 20px;
|
background: #f8f9fb;
|
padding: 10px 15px;
|
border-radius: 4px;
|
}
|
|
.required-label {
|
font-size: 14px;
|
font-weight: bold;
|
color: #606266;
|
}
|
|
.required-label::before {
|
content: '*';
|
color: #f56c6c;
|
margin-right: 4px;
|
}
|
|
.amount-text {
|
color: #409eff;
|
font-weight: bold;
|
}
|
|
.text-danger {
|
color: #f56c6c;
|
}
|
|
.font-bold {
|
font-weight: bold;
|
}
|
|
.mb-20 {
|
margin-bottom: 20px;
|
}
|
|
.mb-10 {
|
margin-bottom: 10px;
|
}
|
|
:deep(.el-input.is-disabled .el-input__wrapper) {
|
background-color: transparent;
|
box-shadow: none;
|
}
|
|
:deep(.el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell) {
|
width: 16%;
|
}
|
</style>
|