Compare commits

..

8 Commits

Author SHA1 Message Date
刘泉佳 78bb38e955 🐛 收款流水认领额度改用收款金额并新增剩余可认领金额列
认领只登记本地,额度由本地累计认领控制,金蝶未匹配金额不再作为上限;批量认领校验复用剩余可认领金额口径。
2026-10-29 20:07:53 +08:00
刘泉佳 2cf966d6e3 feat: 开票申请查看展示每张发票金蝶开票状态与结果备注 2026-10-29 18:17:42 +08:00
刘泉佳 d23a9b00de refactor: 收款流水未匹配金额字段名统一为unmatchAmountRec 2026-10-29 16:13:23 +08:00
刘泉佳 d89fd9db97 feat: 收款流水列表展示匹配与结算金额字段 2026-10-29 16:06:03 +08:00
刘泉佳 94ee14b683 feat: 收款流水列表展示收款人公司与核算组织编码 2026-10-29 15:05:28 +08:00
刘泉佳 a725d3078f fix: 收款流水详情收款金额显示名称修正 2026-10-29 14:12:55 +08:00
刘泉佳 1957a78c00 fix: 收款流水同步接口超时时间延长到30秒 2026-10-29 12:55:03 +08:00
刘泉佳 acfe9bdd74 feat: 收款流水页面适配A301-1数据源并下线推送金蝶相关交互 2026-10-29 11:20:18 +08:00
19 changed files with 110 additions and 377 deletions
+2 -2
View File
@@ -32,11 +32,11 @@ export const refreshMasterData = () =>
timeout: 60000,
});
export const getRisk = (customerCode, creditCode, customerName) =>
export const getRisk = customerCode =>
request({
url: `${baseUrl}/risk`,
method: 'get',
params: { customerCode, creditCode, customerName },
params: { customerCode },
timeout: 30000,
});
-8
View File
@@ -8,20 +8,12 @@ export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get'
export const updateAttachments = data =>
request({ url: `${baseUrl}/attachments`, method: 'post', data });
export const voidClaim = id => request({ url: `${baseUrl}/void`, method: 'post', params: { id } });
export const queryKingdeeResult = () =>
request({ url: `${baseUrl}/query-kingdee-result`, method: 'post' });
export const claimStatusOptions = [
{ label: '已认领', value: 'claimed' },
{ label: '已作废', value: 'voided' },
];
export const pushStatusOptions = [
{ label: '未推送', value: 'none' },
{ label: '已推送', value: 'pushed' },
{ label: '推送失败', value: 'failed' },
];
export const kingdeeBillStatusOptions = [
{ label: '未生成', value: 'none' },
{ label: '已推送', value: 'pushed' },
+4 -9
View File
@@ -2,6 +2,9 @@ import request from '@/axios';
const baseUrl = '/blade-transport/receipt-flow';
/** 收款流水同步需远程拉取金蝶收款单并逐条落库,覆盖全局 10s 超时 */
export const SYNC_TIMEOUT = 30000;
export const getList = (current, size, params) =>
request({ url: `${baseUrl}/list`, method: 'get', params: { current, size, ...params } });
export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get', params: { id } });
@@ -12,9 +15,7 @@ export const getSettlementCandidates = (keyword, flowId) =>
params: { keyword, flowId },
});
export const claim = data => request({ url: `${baseUrl}/claim`, method: 'post', data });
export const sync = () => request({ url: `${baseUrl}/sync`, method: 'post' });
export const pushKingdee = flowId =>
request({ url: `${baseUrl}/push-kingdee`, method: 'post', params: { flowId } });
export const sync = () => request({ url: `${baseUrl}/sync`, method: 'post', timeout: SYNC_TIMEOUT });
export const claimStatusOptions = [
{ label: '未认领', value: 'unclaimed' },
@@ -22,12 +23,6 @@ export const claimStatusOptions = [
{ label: '认领完成', value: 'claimed' },
];
export const pushStatusOptions = [
{ label: '未推送', value: 'none' },
{ label: '已推送', value: 'pushed' },
{ label: '推送失败', value: 'failed' },
];
export const kingdeeBillStatusOptions = [
{ label: '已推送', value: 'pushed' },
{ label: '审核通过', value: 'approved' },
-12
View File
@@ -163,18 +163,6 @@ export const queryKingdeeCustomer = creditCode => {
});
};
// 金蝶供应商编码查询(A901-1):新增客商填写信用代码后即时回显
export const querySupplierCode = creditCode => {
return request({
url: '/blade-transport/customer-archive/query-supplier-code',
method: 'get',
timeout: 30000,
params: {
creditCode,
},
});
};
export const syncKingdee = id => {
return request({
url: '/blade-transport/customer-archive/sync-kingdee',
-8
View File
@@ -621,14 +621,6 @@ export const option = createCrudOption([
addDisplay: false,
editDisplay: false,
},
{
label: '客商风险',
prop: 'customerRisk',
slot: true,
minWidth: 150,
addDisplay: false,
editDisplay: false,
},
{
label: '金蝶合同ID',
prop: 'kingdeeId',
+2 -2
View File
@@ -1,7 +1,7 @@
export const receiptClaimRecordTableColumns = [
{ prop: 'receiptNoticeNo', label: '认领通知单', minWidth: 150, link: true },
{ prop: 'receiptNoticeNo', label: '收款单号', minWidth: 150, link: true },
{ prop: 'payerName', label: '付款人', minWidth: 130 },
{ prop: 'receiptAmount', label: '付款金额', minWidth: 130, money: true },
{ prop: 'receiptAmount', label: '收款金额', minWidth: 130, money: true },
{ prop: 'counterpartyName', label: '对方户名', minWidth: 150 },
{ prop: 'counterpartyAccount', label: '对方账号', minWidth: 170 },
{ prop: 'counterpartyBank', label: '对方开户行', minWidth: 150 },
+12 -7
View File
@@ -1,18 +1,23 @@
export const receiptFlowTableColumns = [
{ prop: 'receiptNoticeNo', label: '认领通知单', minWidth: 150, link: true },
{ prop: 'receiptNoticeNo', label: '收款单号', minWidth: 150, link: true },
{ prop: 'payerName', label: '付款人', minWidth: 130 },
{ prop: 'receiptAmount', label: '付款金额', minWidth: 130, money: true },
{ prop: 'receiptAmount', label: '收款金额', minWidth: 130, money: true },
{ prop: 'counterpartyName', label: '对方户名', minWidth: 150 },
{ prop: 'counterpartyAccount', label: '对方账号', minWidth: 170 },
{ prop: 'counterpartyBank', label: '对方开户行', minWidth: 150 },
{ prop: 'payeeName', label: '收款人(公司)', minWidth: 150 },
//{ prop: 'orgNumber', label: '收款公司编码', minWidth: 130 },
//{ prop: 'openOrgNumber', label: '核算组织编码', minWidth: 130 },
{ prop: 'summary', label: '摘要', minWidth: 160 },
{ prop: 'transactionTime', label: '交易时间', minWidth: 170 },
{ prop: 'claimStatusName', label: '认领状态', minWidth: 110, status: true },
{ prop: 'kingdeeClaimStatusName', label: '公告状态', minWidth: 110 },
{ prop: 'claimedAmount', label: '已认领金额', minWidth: 130, money: true },
{ prop: 'unclaimedAmount', label: '金蝶未认领金额', minWidth: 140, money: true },
{ prop: 'pushStatusName', label: '推送状态', minWidth: 110, status: true },
{ prop: 'pushFailReason', label: '推送失败原因', minWidth: 160 },
{ prop: 'kingdeeBillNo', label: '金蝶收款单号', minWidth: 150 },
{ prop: 'remainingAmount', label: '剩余可认领金额', minWidth: 140, money: true },
{ prop: 'unmatchAmountRec', label: '未匹配金额', minWidth: 140, money: true },
//{ prop: 'matchAmountRec', label: '已匹配收款金额', minWidth: 140, money: true },
//{ prop: 'matchAmountPay', label: '已匹配付款金额', minWidth: 140, money: true },
//{ prop: 'unmatchAmountPay', label: '未匹配付款金额', minWidth: 140, money: true },
//{ prop: 'settleAmount', label: '已结算金额', minWidth: 130, money: true },
//{ prop: 'unsettleAmount', label: '未结算金额', minWidth: 130, money: true },
{ prop: 'kingdeeBillStatusName', label: '金蝶状态', minWidth: 110 },
];
-28
View File
@@ -1,28 +0,0 @@
/**
* 客商/合同风险标签公共展示逻辑(黑名单=金蝶黑名单 A801-5 命中,与后端 HcsCreditCheckSupport 口径一致):
* 等级与黑名单组合展示、等级在前;内部组织客商默认无风险
*/
/** 金蝶黑名单命中判定(客商档案行:失信类别/列入事由任一非空) */
export function isKingdeeBlack(category, reason) {
return !!(category || reason);
}
/** 合同行黑名单判定(推送链路按金蝶命中口径落库 kingdeeBlackStatus=yes) */
export function isContractBlack(blackStatus) {
return String(blackStatus || '').toLowerCase() === 'yes';
}
/** 风险标签文案:riskLevel=C101-1 等级(0/1/2);isBlack=金蝶黑名单命中;customerKind=internal 显示"无风险" */
export function resolveRiskTagName(riskLevel, isBlack, customerKind) {
const levelName = { 2: '高风险', 1: '中风险', 0: '低风险' }[riskLevel] || '';
if (isBlack) return levelName ? `${levelName}、黑名单` : '黑名单';
if (levelName) return levelName;
if (customerKind === 'internal') return '无风险';
return '';
}
/** 是否红色风险标签(黑名单命中或高风险) */
export function isRiskyTag(riskLevel, isBlack) {
return isBlack || riskLevel === '2';
}
@@ -38,7 +38,7 @@
contract.hcsFdId || '-'
}}</el-descriptions-item>
<el-descriptions-item label="客商风险">
<template v-if="contract.hcsRiskLevel || contract.kingdeeBlackStatus">
<template v-if="contract.hcsRiskLevel || contract.hcsBlackStatus">
<el-tag
v-if="isBlacklist"
type="danger"
@@ -130,7 +130,7 @@ export default {
name: 'HcsContractPanel',
components: { UploadFilled },
props: {
// 合同详情行(须含 id/hcsSyncStatus/hcsFdId/hcsRiskLevel/kingdeeBlackStatus/hcsSpecStatus 等)
// 合同详情行(须含 id/hcsSyncStatus/hcsFdId/hcsRiskLevel/hcsBlackStatus/hcsSpecStatus 等)
contract: { type: Object, required: true },
},
emits: ['refresh'],
@@ -152,7 +152,7 @@ export default {
return this.contract.hcsSyncMsg || '';
},
isBlacklist() {
return String(this.contract.kingdeeBlackStatus || '').toLowerCase() === 'yes';
return String(this.contract.hcsBlackStatus || '').toLowerCase() === 'yes';
},
specLabel() {
return SPEC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSpecStatus)?.label || '';
@@ -745,7 +745,7 @@ export default {
// 高风险/黑名单客商提示:按合同推送合规留痕 hcs_risk_level/hcs_black_status 判定(与合同详情口径一致)
riskTipText() {
const riskLevel = String(this.form.hcsRiskLevel || '').trim();
const blacklist = String(this.form.kingdeeBlackStatus || '').toLowerCase() === 'yes';
const blacklist = String(this.form.hcsBlackStatus || '').toLowerCase() === 'yes';
if (riskLevel !== '2' && !blacklist) return '';
const parties = [this.form.partyA, this.form.partyB]
.map(name => String(name || '').trim())
+1 -21
View File
@@ -92,18 +92,6 @@
<span v-else>-</span>
</template>
<!-- 客商风险:等级与金蝶黑名单组合展示(推送链路按金蝶命中口径落库),复用公共展示方法 -->
<template #customerRisk="{ row }">
<el-tag
v-if="contractRiskTagName(row)"
:type="isContractRisky(row) ? 'danger' : row.hcsRiskLevel === '1' ? 'warning' : 'success'"
class="status-text"
>
{{ contractRiskTagName(row) }}
</el-tag>
<span v-else>-</span>
</template>
<template #menu="{ row }">
<el-link
v-if="hasPermission(`${config.permission}_view`)"
@@ -1103,7 +1091,6 @@ import { exportBlob } from '@/api/common';
import * as api from '@/api/business/contract-manage';
import { createFromContract } from '@/api/business/contract-change';
import { repush as hcsRepush, repushBatch as hcsRepushBatch, getRisk } from '@/api/business/hcs';
import { isContractBlack, isRiskyTag, resolveRiskTagName } from '@/utils/risk-display';
import { Position } from '@element-plus/icons-vue';
import { getMkPublicDetail } from '@/api/mk-process';
import { getList as getProjectList } from '@/api/business/project-apply';
@@ -1636,7 +1623,7 @@ export default {
// 详情风险客商提示(基本信息备注后展示):按推送留痕 hcs_risk_level/hcs_black_status 判定(甲乙取高/任一),文案与后端 riskTip 一致
detailRiskTipText() {
const riskLevel = String(this.detailRow.hcsRiskLevel || '').trim();
const blacklist = String(this.detailRow.kingdeeBlackStatus || '').toLowerCase() === 'yes';
const blacklist = String(this.detailRow.hcsBlackStatus || '').toLowerCase() === 'yes';
if (riskLevel !== '2' && !blacklist) return '';
const parties = [this.detailRow.partyA, this.detailRow.partyB]
.map(name => String(name || '').trim())
@@ -1769,13 +1756,6 @@ export default {
hcsSyncLabel(value) {
return { unsynced: '待推送', synced: '已推送', failed: '推送失败' }[value] || value;
},
// ==================== 客商风险(推送链路按金蝶黑名单命中口径落库,展示复用公共方法) ====================
contractRiskTagName(row) {
return resolveRiskTagName(row.hcsRiskLevel, isContractBlack(row.kingdeeBlackStatus));
},
isContractRisky(row) {
return isRiskyTag(row.hcsRiskLevel, isContractBlack(row.kingdeeBlackStatus));
},
hcsSyncTagType(value) {
return { unsynced: 'info', synced: 'success', failed: 'danger' }[value] || 'info';
},
+11 -3
View File
@@ -883,6 +883,8 @@ export default {
// 收款方 C101-1 风险查询留档:risky=高风险/黑名单,tip=后端拼装提示文案
payeeRisky: false,
payeeRiskTip: '',
payeeRiskLevel: '',
payeeBlackStatus: '',
attachmentRuleLoading: false,
// 最近一次从结算单同步过来的附件 URL,切换结算单时用于整批替换
linkedSettlementAttachmentUrls: [],
@@ -1063,12 +1065,14 @@ export default {
.toUpperCase();
return ['D', 'D级', 'E', 'E级', 'F', 'F级'].includes(level) || level.includes('高风险');
},
// 高风险/黑名单收款方提示(C101-1,强制拦截在后端提交校验与金蝶推送门禁):
// 等级/黑名单明细以后端 riskTip 为准(黑名单=金蝶命中、等级=C101-1,统一口径),前端不再重复拼接
// 高风险/黑名单收款方提示(C101-1,同合同新增页风险客商提示口径;强制拦截在后端提交校验与金蝶推送门禁)
payeeRiskText() {
if (!this.payeeRisky) return '';
const levelMap = { 0: '低风险', 1: '中风险', 2: '高风险' };
const level = levelMap[this.payeeRiskLevel] || '未知';
const blacklist = String(this.payeeBlackStatus || '').trim().toLowerCase() === 'yes' ? '是' : '否';
const tip = this.payeeRiskTip || `收款方[${this.form.payeeName || ''}]为高风险/黑名单客商`;
return `${tip},按集团规定不可直接付款,提交前须上传「特批附件」类型附件,审批通过后将自动发起付款特批`;
return `风险等级:${level}|黑名单:${blacklist}。${tip},按集团规定不可直接付款,提交前须上传「特批附件」类型附件,审批通过后将自动发起付款特批`;
},
requiredAttachmentMaterials() {
if (this.form.paymentType === 'project_advance') {
@@ -2337,6 +2341,8 @@ export default {
this.payeeCustomerLevel = '';
this.payeeRisky = false;
this.payeeRiskTip = '';
this.payeeRiskLevel = '';
this.payeeBlackStatus = '';
if (!partyBName) {
if (!preserveCurrentValue) this.clearReceiptAccount();
return;
@@ -2375,6 +2381,8 @@ export default {
const risk = this.unwrapData(await getCustomerRisk(detail.customerCode)) || {};
this.payeeRisky = Boolean(risk.risky);
this.payeeRiskTip = risk.riskTip || '';
this.payeeRiskLevel = risk.fdRiskLevel || '';
this.payeeBlackStatus = risk.fdBlackStatus || '';
} catch (error) {
// 忽略风险查询异常,提交时后端 C101-1 强校验兜底
}
@@ -4,7 +4,7 @@
<section-card title="收款信息">
<el-row :gutter="32">
<el-col :span="6">
<el-form-item label="认领通知单">
<el-form-item label="收款单号">
<el-input v-model="form.receiptNoticeNo" disabled />
</el-form-item>
</el-col>
+4 -34
View File
@@ -3,7 +3,7 @@
<section-card v-if="searchVisible" class="claim-record-page__search">
<el-form :model="query" label-position="right" label-width="88px" @submit.prevent>
<div class="claim-record-page__search-grid">
<el-form-item label="认领通知单">
<el-form-item label="收款单号">
<el-input v-model="query.receiptNoticeNo" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="对方户名">
@@ -56,16 +56,6 @@
<el-form-item v-show="searchExpanded" label="所属组织">
<el-input v-model="query.claimerDeptName" clearable placeholder="请输入" />
</el-form-item>
<el-form-item v-show="searchExpanded" label="推送状态">
<el-select v-model="query.pushStatus" clearable placeholder="请选择">
<el-option
v-for="item in pushStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-show="searchExpanded" label="金蝶单据状态">
<el-select v-model="query.kingdeeBillStatus" clearable placeholder="请选择">
<el-option
@@ -93,9 +83,6 @@
<section class="claim-record-page__table-panel">
<div class="claim-record-page__toolbar">
<el-button :loading="refreshing" @click="handleRefreshKingdeeResult">
查询金蝶认领结果
</el-button>
<table-header-tools
:columns="columns"
v-model:visible-column-keys="visibleColumnKeys"
@@ -131,11 +118,7 @@
<el-table-column label="操作" width="120" fixed="right" align="center">
<template #default="{ row }">
<el-link
v-if="
hasPermission('receipt_claim_record_void') &&
row.claimStatus === 'claimed' &&
row.pushStatus !== 'pushed'
"
v-if="hasPermission('receipt_claim_record_void') && row.claimStatus === 'claimed'"
type="danger"
@click="handleVoid(row)"
>作废</el-link
@@ -181,7 +164,6 @@ const emptyQuery = () => ({
claimDateRange: [],
claimerDeptName: '',
kingdeeBillStatus: '',
pushStatus: '',
});
export default {
@@ -195,17 +177,15 @@ export default {
searchExpanded: false,
// 与 avue-crud 一致:搜索项数量超过 searchIndex(默认一行 4 项)时才显示“展开/收起”
searchIndex: 4,
searchItemCount: 11,
searchItemCount: 10,
searchVisible: true,
tableSize: 'default',
claimStatusOptions: api.claimStatusOptions,
kingdeeBillStatusOptions: api.kingdeeBillStatusOptions,
pushStatusOptions: api.pushStatusOptions,
columns: receiptClaimRecordTableColumns,
visibleColumnKeys: receiptClaimRecordTableColumns.map(column => column.prop),
rows: [],
loading: false,
refreshing: false,
page: { current: 1, size: 10, total: 0 },
};
},
@@ -272,7 +252,7 @@ export default {
},
async handleVoid(row) {
await this.$confirm(
'作废后将回退本次认领金额,是否继续?',
'作废后将回退本次认领金额,并同步作废合同合规系统认领记录,是否继续?',
'作废认领记录',
{ type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }
);
@@ -280,16 +260,6 @@ export default {
this.$message.success('作废成功');
await this.loadTable();
},
async handleRefreshKingdeeResult() {
this.refreshing = true;
try {
const updated = Number(this.unwrapData(await api.queryKingdeeResult()) || 0);
this.$message.success(`金蝶认领结果回写完成,更新${updated}条`);
await this.loadTable();
} finally {
this.refreshing = false;
}
},
hasPermission(code) {
return this.isAdmin || this.validData(this.permission?.[code], false);
},
+8 -14
View File
@@ -7,7 +7,7 @@
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column
prop="receiptNoticeNo"
label="认领通知单"
label="收款单号"
min-width="160"
show-overflow-tooltip
/>
@@ -43,8 +43,8 @@
<el-table-column label="已认领金额" min-width="130" align="right">
<template #default="{ row }">{{ formatMoney(row.claimedAmount) }}</template>
</el-table-column>
<el-table-column label="金蝶未认领金额" min-width="140" align="right">
<template #default="{ row }">{{ formatMoney(row.unclaimedAmount) }}</template>
<el-table-column label="未匹配金额" min-width="140" align="right">
<template #default="{ row }">{{ formatMoney(row.unmatchAmountRec) }}</template>
</el-table-column>
<el-table-column label="剩余可认领金额" min-width="140" align="right">
<template #default="{ row }">{{ formatMoney(flowRemaining(row)) }}</template>
@@ -96,7 +96,7 @@
formatMoney(row.settlement.claimedReceiptAmount)
}}</template>
</el-table-column>
<el-table-column label="认领通知单" min-width="180">
<el-table-column label="收款单号" min-width="180">
<template #default="{ row }">{{ row.flow.receiptNoticeNo || '-' }}</template>
</el-table-column>
<el-table-column label="收款金额" min-width="140" align="right">
@@ -302,10 +302,8 @@ export default {
);
},
flowRemaining(flow) {
const unclaimed = Number(flow.unclaimedAmount || 0);
const receipt = Number(flow.receiptAmount || 0);
const ceiling = unclaimed > 0 && receipt > 0 ? Math.min(unclaimed, receipt) : unclaimed || receipt;
return Math.max(ceiling - Number(flow.claimedAmount || 0), 0);
// 认领只登记本地:剩余可认领 = 收款金额 − 已认领金额
return Math.max(Number(flow.receiptAmount || 0) - Number(flow.claimedAmount || 0), 0);
},
maxAllocation(row) {
const current = Number(row.settlement.allocations?.[row.flow.id] || 0);
@@ -355,12 +353,8 @@ export default {
}
for (const flow of this.flows) {
const total = this.flowAllocatedTotal(flow.id);
const remaining = Math.max(
Number(flow.unclaimedAmount || 0) - Number(flow.claimedAmount || 0),
0
);
if (total > remaining + 0.001) {
this.$message.warning(`流水${flow.receiptNoticeNo}的累计认领金额不能超过公告未认领金额`);
if (total > this.flowRemaining(flow) + 0.001) {
this.$message.warning(`流水${flow.receiptNoticeNo}的累计认领金额不能超过剩余可认领金额`);
return;
}
}
+7 -8
View File
@@ -12,7 +12,7 @@
<section-card title="收款信息">
<el-row :gutter="32">
<el-col :span="6"
><el-form-item label="认领通知单"
><el-form-item label="收款单号"
><el-input v-model="form.receiptNoticeNo" disabled /></el-form-item
></el-col>
<el-col :span="6"
@@ -48,8 +48,8 @@
><el-input :model-value="formatMoney(form.claimedAmount)" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="金蝶未认领金额"
><el-input :model-value="formatMoney(form.unclaimedAmount)" disabled /></el-form-item
><el-form-item label="未匹配金额"
><el-input :model-value="formatMoney(form.unmatchAmountRec)" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="剩余可认领金额"
@@ -205,7 +205,7 @@ const emptyForm = () => ({
transactionTime: '',
detailSerialNo: '',
claimedAmount: 0,
unclaimedAmount: 0,
unmatchAmountRec: 0,
claimerName: '',
claimerDeptName: '',
claimDate: '',
@@ -252,11 +252,10 @@ export default {
);
},
claimableAmount() {
const unclaimed = Number(this.form.unclaimedAmount || 0);
// 认领只登记本地:剩余可认领 = 收款金额 − 已认领金额
const receipt = Number(this.form.receiptAmount || 0);
const ceiling =
unclaimed > 0 && receipt > 0 ? Math.min(unclaimed, receipt) : unclaimed || receipt;
return Math.max(ceiling - Number(this.form.claimedAmount || 0), 0);
const claimed = Number(this.form.claimedAmount || 0);
return Math.max(receipt - claimed, 0);
},
},
created() {
+10 -77
View File
@@ -3,7 +3,7 @@
<section v-if="searchVisible" class="receipt-flow-page__search">
<el-form :model="query" label-position="right" label-width="88px" @submit.prevent>
<div class="receipt-flow-page__search-grid">
<el-form-item label="认领通知单">
<el-form-item label="收款单号">
<el-input v-model="query.receiptNoticeNo" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="对方户名">
@@ -15,16 +15,6 @@
<el-form-item label="对方账号">
<el-input v-model="query.counterpartyAccount" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="推送状态">
<el-select v-model="query.pushStatus" clearable placeholder="请选择">
<el-option
v-for="item in pushStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="认领状态">
<el-select v-model="query.claimStatus" clearable placeholder="请选择">
<el-option
@@ -159,23 +149,10 @@
<span v-else>{{ displayValue(row[column.prop]) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="170" fixed="right" align="center">
<el-table-column label="操作" width="100" fixed="right" align="center">
<template #default="{ row }">
<el-link
v-if="canClaim(row)"
type="primary"
:disabled="pushingId === row.id"
@click="openClaim(row)"
>认领</el-link
>
<el-link
v-if="canPush(row)"
type="warning"
:disabled="pushingId === row.id"
@click="handlePush(row)"
>推送金蝶</el-link
>
<span v-if="!canClaim(row) && !canPush(row)">-</span>
<el-link v-if="canClaim(row)" type="primary" @click="openClaim(row)">认领</el-link>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
@@ -201,13 +178,13 @@
>
<div v-loading="detailDialog.loading">
<el-descriptions :column="3" border>
<el-descriptions-item label="认领通知单">
<el-descriptions-item label="收款单号">
{{ displayValue(detailDialog.data.receiptNoticeNo) }}
</el-descriptions-item>
<el-descriptions-item label="付款人">
{{ displayValue(detailDialog.data.payerName) }}
</el-descriptions-item>
<el-descriptions-item label="付款金额">
<el-descriptions-item label="收款金额">
{{ formatMoney(detailDialog.data.receiptAmount) }}
</el-descriptions-item>
<el-descriptions-item label="交易时间">
@@ -236,13 +213,8 @@
<el-descriptions-item label="剩余可认领金额">
{{ formatMoney(detailDialog.data.remainingAmount) }}
</el-descriptions-item>
<el-descriptions-item label="金蝶未认领金额">
{{ formatMoney(detailDialog.data.unclaimedAmount) }}
</el-descriptions-item>
<el-descriptions-item label="推送状态">
<el-tag :type="statusType(detailDialog.data, { prop: 'pushStatusName' })">
{{ displayValue(detailDialog.data.pushStatusName) }}
</el-tag>
<el-descriptions-item label="未匹配金额">
{{ formatMoney(detailDialog.data.unmatchAmountRec) }}
</el-descriptions-item>
<el-descriptions-item label="金蝶收款单号">
{{ displayValue(detailDialog.data.kingdeeBillNo) }}
@@ -250,9 +222,6 @@
<el-descriptions-item label="金蝶状态">
{{ displayValue(detailDialog.data.kingdeeBillStatusName) }}
</el-descriptions-item>
<el-descriptions-item label="推送失败原因">
{{ displayValue(detailDialog.data.pushFailReason) }}
</el-descriptions-item>
<el-descriptions-item label="摘要">
{{ displayValue(detailDialog.data.summary) }}
</el-descriptions-item>
@@ -267,9 +236,7 @@
</el-table-column>
<el-table-column prop="operatorName" label="认领人" min-width="120" align="center" />
<el-table-column label="状态变化" min-width="170" align="center">
<template #default="{ row }">
{{ row.actionType === 'push' ? row.actionName : claimStatusChange(row) }}
</template>
<template #default="{ row }">{{ claimStatusChange(row) }}</template>
</el-table-column>
<el-table-column prop="content" label="认领说明" min-width="220" />
</el-table>
@@ -297,7 +264,6 @@ const emptyQuery = () => ({
summary: '',
detailSerialNo: '',
claimStatus: '',
pushStatus: '',
kingdeeBillStatus: '',
receiptAmountStart: undefined,
receiptAmountEnd: undefined,
@@ -314,7 +280,6 @@ export default {
ArrowUp,
query: emptyQuery(),
claimStatusOptions: api.claimStatusOptions,
pushStatusOptions: api.pushStatusOptions,
kingdeeBillStatusOptions: api.kingdeeBillStatusOptions,
columns: receiptFlowTableColumns,
visibleColumnKeys: receiptFlowTableColumns.map(column => column.prop),
@@ -324,7 +289,6 @@ export default {
selectedRows: [],
loading: false,
syncing: false,
pushingId: null,
searchExpanded: false,
// 与 avue-crud 一致:搜索项数量超过 searchIndex(默认一行 4 项)时才显示“展开/收起”
searchIndex: 4,
@@ -389,7 +353,7 @@ export default {
this.syncing = true;
try {
const syncedCount = Number(this.unwrapData(await api.sync()) || 0);
this.$message.success(`流水同步完成,共同步${syncedCount}条认领公告`);
this.$message.success(`流水同步完成,共同步${syncedCount}条收款流水`);
await this.loadTable();
} finally {
this.syncing = false;
@@ -448,38 +412,7 @@ export default {
canClaim(row) {
return this.hasPermission('receipt_flow_claim') && Number(row.remainingAmount || 0) > 0;
},
canPush(row) {
return (
this.hasPermission('receipt_flow_push') &&
!!row.billId &&
row.claimStatus === 'claimed' &&
row.pushStatus !== 'pushed'
);
},
async handlePush(row) {
try {
await this.$confirm(
`确认将流水【${row.receiptNoticeNo || row.detailSerialNo || ''}】推送金蝶生成收款单?`,
'推送金蝶',
{ type: 'warning' }
);
} catch {
return;
}
this.pushingId = row.id;
try {
const data = this.unwrapData(await api.pushKingdee(row.id));
const billNo = data?.kingdeeBillNo;
this.$message.success(billNo ? `推送成功,金蝶收款单号:${billNo}` : '推送成功');
await this.loadTable();
} finally {
this.pushingId = null;
}
},
statusType(row, column) {
if (column?.prop === 'pushStatusName') {
return { pushed: 'success', failed: 'danger' }[row.pushStatus] || 'info';
}
return (
{ claimed: 'success', partial: 'warning', unclaimed: 'info' }[row.claimStatus] || ''
);
@@ -559,7 +559,7 @@
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column
prop="receiptNoticeNo"
label="认领通知单"
label="收款单号"
min-width="160"
align="center"
/>
@@ -708,9 +708,7 @@
:rows="changeRecordDetailRows"
/>
<template v-if="!pageMode" #footer>
<el-button v-if="canSync" type="primary" plain :loading="syncLoading" @click="handleSync"
>同步</el-button
>
<el-button v-if="editable" @click="handleSyncPlaceholder">同步</el-button>
<el-button @click="visible = false">取消</el-button>
<el-button
v-if="editable"
@@ -726,9 +724,7 @@
>
</template>
<div v-if="pageMode && !publicMode" class="formal-editor__page-actions">
<el-button v-if="canSync" type="primary" plain :loading="syncLoading" @click="handleSync"
>同步</el-button
>
<el-button v-if="editable" @click="handleSyncPlaceholder">同步</el-button>
<el-button @click="visible = false">取消</el-button>
<el-button
v-if="editable"
@@ -1055,7 +1051,6 @@ import {
claimInvoices as claimInvoicesApi,
queryInvoicePool,
save,
syncKingdee,
} from '@/api/settlement/formalSettlement';
import {
getDetail as getPreSettlementDetail,
@@ -1133,7 +1128,6 @@ export default {
loading: false,
draftSaving: false,
saving: false,
syncLoading: false,
form: createFormalSettlementForm(),
sources: [],
details: [],
@@ -1277,16 +1271,6 @@ export default {
editable() {
return !this.readonly;
},
// 查看页面「同步」按钮:仅已审批且未同步金蝶(含推送失败)的单据显示,口径与后端推送闸门一致
canSync() {
return (
this.readonly &&
!this.publicMode &&
this.hasPermission('sync') &&
this.form.approvalStatus === 'approved' &&
this.form.kingdeeSyncStatus !== 'synced'
);
},
isAdmin() {
return isAdministrator(this.userInfo);
},
@@ -2903,30 +2887,8 @@ export default {
remark: this.form.remark,
};
},
// 查看页面单张推送金蝶(A102-2/A302-2):结果与异常原因弹框展示,成败后均刷新详情与列表
async handleSync() {
if (this.syncLoading) return;
this.syncLoading = true;
try {
const res = await syncKingdee(this.form.id, { meta: { suppressErrorMessage: true } });
await this.loadFormalDetail(this.recordId);
this.$emit('success');
let message = `同步成功:${this.unwrapData(res) || '-'}`;
if (['pending', 'partial'].includes(this.form.attachmentSyncStatus)) {
message += '\n该单附件未全部上传,可重推补传';
}
await this.$alert(message, '同步结果', { type: 'success', confirmButtonText: '知道了' });
} catch (error) {
const reason = String(error?.message || error || '未知错误').trim() || '未知错误';
await this.loadFormalDetail(this.recordId);
this.$emit('success');
await this.$alert(`同步失败,${reason}`, '同步结果', {
type: 'error',
confirmButtonText: '知道了',
});
} finally {
this.syncLoading = false;
}
handleSyncPlaceholder() {
this.$message.info('开发中');
},
async handleDraftSave() {
// 保存草稿:不做表单校验,直接落草稿并返回列表刷新
+39 -96
View File
@@ -49,24 +49,24 @@
</template>
<template #customerRisk="{ row }">
<el-tooltip
v-if="customerRowRisky(row)"
v-if="customerRiskMap[row.customerCode] && customerRiskMap[row.customerCode].risky"
placement="top"
effect="dark"
popper-class="customer-risk-popper"
>
<template #content>
<div>{{ row.hcsRiskDetail || '该客商为高风险/黑名单客商' }}</div>
<div>{{ customerRiskMap[row.customerCode].riskTip || '该客商为高风险/黑名单客商' }}</div>
</template>
<el-tag type="danger" class="status-text">
{{ customerRowRiskName(row) }}
{{ customerRiskLabel(customerRiskMap[row.customerCode]) }}
</el-tag>
</el-tooltip>
<el-tag
v-else-if="customerRowRiskName(row)"
:type="row.hcsRiskLevel === '1' ? 'warning' : 'success'"
v-else-if="customerRiskMap[row.customerCode]"
:type="customerRiskMap[row.customerCode].fdRiskLevel === '1' ? 'warning' : 'success'"
class="status-text"
>
{{ customerRowRiskName(row) }}
{{ customerRiskLabel(customerRiskMap[row.customerCode]) }}
</el-tag>
<span v-else>-</span>
</template>
@@ -291,17 +291,6 @@
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="供应商编码" prop="supplierCode">
<el-input
v-model="archiveForm.supplierCode"
maxlength="64"
show-word-limit
disabled
placeholder="金蝶供应商编码,系统自动获取"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客商类型" prop="customerKind">
<el-select
@@ -383,7 +372,7 @@
<template #content>
<div>{{ archiveNameRiskTip || '该客商为高风险/黑名单客商' }}</div>
</template>
<div class="archive-name-risk">{{ archiveNameRiskLabel }}</div>
<div class="archive-name-risk">高风险/黑名单客商</div>
</el-tooltip>
</el-form-item>
</el-col>
@@ -1606,11 +1595,9 @@ import {
getScoreTemplate,
recognizeBusinessLicenseOcr,
queryKingdeeCustomer,
querySupplierCode,
syncKingdee,
} from '@/api/vehicle/customer-archive';
import { getRisk, getRiskByName } from '@/api/business/hcs';
import { isKingdeeBlack, isRiskyTag, resolveRiskTagName } from '@/utils/risk-display';
import { getRiskBatch, getRiskByName } from '@/api/business/hcs';
import {
getList as getCreditScoreQuantificationList,
getDetail as getCreditScoreQuantificationDetail,
@@ -1712,9 +1699,9 @@ export default {
deptSearchInitialized: false,
loading: true,
data: [],
customerRiskMap: {},
archiveNameRisky: false,
archiveNameRiskTip: '',
archiveNameRiskLabel: '',
page: {
pageSize: 10,
pageSizes: [10, 20, 50, 100],
@@ -1934,11 +1921,6 @@ export default {
searchPlaceholder: '请输入',
minWidth: 140,
},
{
label: '供应商编码',
prop: 'supplierCode',
minWidth: 140,
},
{
label: '客商简称',
prop: 'shortName',
@@ -1981,13 +1963,7 @@ export default {
label: '客商风险',
prop: 'customerRisk',
slot: true,
search: true,
searchType: 'select',
searchOrder: 4,
searchPlaceholder: '请选择',
minWidth: 150,
// 选项走业务字典 customer_risk(initBusinessDictionaries 加载:dictKey=筛选值,dictValue=名称),未配置时下拉为空
dicData: [],
minWidth: 100,
},
{
label: '客户性质',
@@ -2493,7 +2469,6 @@ export default {
const form = this.archiveForm;
form.kingdeeStatus = 'none';
form.customerCode = '';
form.supplierCode = '';
form.fullName = '';
form.legalPerson = '';
form.registeredCapital = '';
@@ -2509,7 +2484,6 @@ export default {
handleKingdeeCreditCodeChange(val) {
this.kingdeeBlocked = false;
if (this.readonly || this.archiveForm.id) return;
this.clearArchiveNameRisk();
if (this.archiveForm.kingdeeStatus === 'exist') {
this.resetKingdeeBackfill();
}
@@ -2518,21 +2492,7 @@ export default {
.toUpperCase();
if (code.length !== 18 || this.unifiedCreditCodeError(code)) return;
clearTimeout(this.kingdeeQueryTimer);
this.kingdeeQueryTimer = setTimeout(() => {
this.fetchKingdeeCustomer(code);
this.fetchSupplierCode(code);
}, 500);
},
// 新增态下信用代码录入后即时查询金蝶供应商编码(A901-1)回显,查询失败静默留空,保存/同步时后端兜底落库
async fetchSupplierCode(code) {
try {
const res = await querySupplierCode(code);
// 防止响应返回时用户已修改代码
if (String(this.archiveForm.unifiedCreditCode || '').trim().toUpperCase() !== code) return;
this.archiveForm.supplierCode = res?.data?.data || '';
} catch (error) {
// 忽略查询异常,编码留空
}
this.kingdeeQueryTimer = setTimeout(() => this.fetchKingdeeCustomer(code), 500);
},
async fetchKingdeeCustomer(code) {
let customer = null;
@@ -2740,7 +2700,6 @@ export default {
currentNode: '当前节点',
currentProcessor: '当前处理人',
customerCode: '客商编号',
supplierCode: '供应商编码',
shortName: '客商简称',
fullName: '客商名称',
customerType: '客商分类',
@@ -3198,15 +3157,6 @@ export default {
});
},
initBusinessDictionaries() {
// 客商风险筛选项走业务字典 customer_risk(dictKey=筛选值 blacklist/high/middle/low,dictValue=名称),字典缺失保留固定兜底项
getDictionary({ code: 'customer_risk' }).then(res => {
const options = (res.data.data || [])
.filter(item => item.dictKey !== '-1')
.map(item => ({ label: item.dictValue, value: item.dictKey }));
if (options.length) {
this.findColumn(this.option.column, 'customerRisk').dicData = options;
}
});
this.loadDictOptions('nature_of_client', 'customerNatureOptions', [], options => {
const customerNatureColumn = this.findColumn(this.option.column, 'customerNature');
customerNatureColumn.dicData = [{ label: '全部', value: '' }, ...options];
@@ -5555,9 +5505,7 @@ export default {
this.onLoad(this.page);
},
searchChange(params, done) {
// Avue 只回传非空筛选项,被清空的字段不会出现在 params 里,
// 因此查询条件必须整体重建,不能与上一次的 this.query 合并,否则清空后仍会带上次的值
this.query = this.buildQuery(params, true);
this.query = this.buildQuery(params);
this.page.currentPage = 1;
this.onLoad(this.page, this.query);
done();
@@ -5586,56 +5534,52 @@ export default {
const pageData = res.data.data;
this.page.total = pageData.total;
this.data = pageData.records;
this.loadCustomerRisk(this.data);
this.selectionClear();
})
.finally(() => {
this.loading = false;
});
},
// 黑名单判定:仅以金蝶黑名单(A801-5)命中为准(失信类别/列入事由任一非空);C101-1 的 hcsBlackStatus 不作为黑名单依据
customerRowIsBlack(row) {
return isKingdeeBlack(row.kingdeeBlackCategory, row.kingdeeBlackReason);
},
// 客商风险名称(每日定时刷新落库字段):等级与黑名单组合展示(如"低风险、黑名单");内部组织默认无风险;未查询显示"-"
customerRowRiskName(row) {
return resolveRiskTagName(row.hcsRiskLevel, this.customerRowIsBlack(row), row.customerKind);
},
// 高风险/黑名单判定(与列表筛选口径一致,命中显示红色标签)
customerRowRisky(row) {
return isRiskyTag(row.hcsRiskLevel, this.customerRowIsBlack(row));
// 客商风险标签文案:后端 riskLevelName 转义(黑名单/高风险/中风险/低风险),缺失时兜底
customerRiskLabel(risk) {
if (!risk) return '';
return risk.riskLevelName || (risk.risky ? '风险客商' : '正常');
},
// 客商名称风险校验(C101-1,仅展示不作为安全边界):名称失焦/回显时查询,
// risky 在名称框下展示红色提示,悬浮见风险项明细;查询失败静默不阻断填写,提交链路由后端强校验兜底
// 清空客商名称下风险提示(信用代码变化后原按旧编号+信用代码查询的结果失效)
clearArchiveNameRisk() {
checkArchiveNameRisk() {
this.archiveNameRisky = false;
this.archiveNameRiskTip = '';
this.archiveNameRiskLabel = '';
},
checkArchiveNameRisk() {
this.clearArchiveNameRisk();
const customerName = String(this.archiveForm.fullName || '').trim();
if (!customerName) return;
// 新增客商档案本地反查不到编号:金蝶带出后表单已有客商编号,优先按编号查 C101-1,
// 并传信用代码供金蝶黑名单(A801-5)兜底;无编号时按名称反查档案
const customerCode = String(this.archiveForm.customerCode || '').trim();
const request = customerCode
? getRisk(customerCode, String(this.archiveForm.unifiedCreditCode || '').trim(), customerName)
: getRiskByName(customerName);
request
getRiskByName(customerName)
.then(res => {
const risk = (res.data && res.data.data) || {};
this.archiveNameRisky = risk.risky === true;
this.archiveNameRiskTip = risk.riskTip || '';
// 红字与列表标签同口径(risk-display):等级按合同系统返回值翻译,黑名单按金蝶命中标记独立展示,组合不分家
this.archiveNameRiskLabel = this.archiveNameRisky
? (resolveRiskTagName(risk.fdRiskLevel, risk.kingdeeBlackHit === true) || '高风险/黑名单') + '客商'
: '';
})
.catch(() => {
// 忽略风险查询异常,表单正常填写
});
},
// 客商列表风险批量查询(C101-1,仅展示不作为安全边界):失败静默,行内显示"-",提交链路由后端强校验兜底
loadCustomerRisk(rows) {
this.customerRiskMap = {};
const customerCodes = [...new Set(rows.map(row => row.customerCode).filter(Boolean))];
if (!customerCodes.length) return;
getRiskBatch(customerCodes)
.then(res => {
const riskMap = {};
((res.data && res.data.data) || []).forEach(risk => {
if (risk && risk.customerCode) riskMap[risk.customerCode] = risk;
});
this.customerRiskMap = riskMap;
})
.catch(() => {
// 忽略风险查询异常,列表正常展示
});
},
handleExport() {
this.$confirm('是否导出客商档案数据?', '提示', {
confirmButtonText: '确定',
@@ -5663,10 +5607,9 @@ export default {
[this.website.tokenHeader]: getToken(),
};
},
// replace=true 表示以本次搜索条件为准整体重建(清空字段不会回传,需丢弃旧条件);
// 翻页、刷新等场景传 false,沿用上一次的查询条件
buildQuery(params = {}, replace = false) {
const merged = replace ? { ...params } : { ...this.query, ...params };
buildQuery(params = {}) {
// 新条件覆盖旧条件;本次显式清空的字段必须从查询里去掉,避免第二次搜索仍带着上一次的客商名称。
const merged = { ...this.query, ...params };
Object.keys(params).forEach(key => {
const value = params[key];
if (