8 Commits

Author SHA1 Message Date
刘泉佳 a554ccf38e 🐛 axios网络级失败(502/504/超时)补充统一错误提示,修复接口报错页面无感知 2026-09-21 21:20:57 +08:00
刘泉佳 a748e52551 开票申请商品行选择开票内容时快照税收分类编码并随保存提交 2026-09-21 19:14:57 +08:00
刘泉佳 e89e78b527 正式结算单认领查询对接发票云全票池(多条件搜索+服务端分页) 2026-09-20 19:41:37 +08:00
刘泉佳 2a03991297 客商档案金蝶回填优化:放宽锁定字段、信用代码可重新输入并修复查询误判 2026-09-20 16:06:03 +08:00
刘泉佳 58aaf79445 客商档案新增金蝶客商查询回填与同步金蝶重推入口 2026-09-18 20:01:29 +08:00
刘泉佳 f067518c0e fix: 付款申请批量同步金蝶失败时展示失败原因
- 批量同步后解析返回结果,按"同步失败"标识区分成功与失败条目
- 部分失败时以 warning 提示成功/失败条数及具体失败原因(停留8秒),全部成功仍提示已同步条数
2026-09-18 11:58:00 +08:00
刘泉佳 11460f3378 付款管理新增同步付款结果按钮并扩展金蝶单据状态选项 2026-09-18 11:57:59 +08:00
刘泉佳 7f543937fb 结算单新增时自动查询BFI汇率
- currency.js: 新增getBfiExchangeRate接口调用
- pre-settlement-editor.vue:
  - 选择合同后同步结算币种并自动查询当天BFI汇率
  - 汇率日期变更时自动查询BFI汇率并回填结算汇率
  - 新增时自动设置当天日期并查询汇率
- formal-settlement-editor.vue:
  - 选择合同后同步结算币种并自动查询当天BFI汇率
  - 汇率日期变更时自动查询BFI汇率并回填结算汇率
  - 新增时自动查询当天BFI汇率
2026-09-16 14:14:53 +08:00
10 changed files with 461 additions and 103 deletions
+11
View File
@@ -58,3 +58,14 @@ export const changeStatus = (id, status) => {
},
});
};
export const getBfiExchangeRate = (currencyCode, effectdate) => {
return request({
url: '/blade-transport/bfi/exchange-rate',
method: 'get',
params: {
currencyCode,
effectdate,
},
});
};
+5
View File
@@ -20,6 +20,8 @@ export const syncKingdee = id =>
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', params: { id } });
export const syncKingdeeBatch = ids =>
request({ url: `${baseUrl}/sync-kingdee-batch`, method: 'post', data: ids });
export const syncKingdeeResult = () =>
request({ url: `${baseUrl}/sync-kingdee-result`, method: 'post' });
export const paymentTypeOptions = [
{ label: '项目预付', value: 'project_advance' },
@@ -36,5 +38,8 @@ export const approvalStatusOptions = [
export const kingdeeStatusOptions = [
{ label: '未生成', value: 'unsynced' },
{ label: '已生成', value: 'synced' },
{ label: '付款中', value: 'paying' },
{ label: '已付款', value: 'paid' },
{ label: '已关闭', value: 'closed' },
{ label: '生成失败', value: 'failed' },
];
+2
View File
@@ -48,3 +48,5 @@ export const applyPayments = data =>
request({ url: `${baseUrl}/apply-payments`, method: 'post', data });
export const claimInvoices = data =>
request({ url: `${baseUrl}/claim-invoices`, method: 'post', data });
export const queryInvoicePool = params =>
request({ url: `${baseUrl}/invoice-pool`, method: 'get', params });
+20
View File
@@ -113,6 +113,26 @@ export const getScoreTemplate = quantificationId => {
});
};
export const queryKingdeeCustomer = creditCode => {
return request({
url: '/blade-transport/bfi/customer/query-by-credit-code',
method: 'get',
params: {
creditCode,
},
});
};
export const syncKingdee = id => {
return request({
url: '/blade-transport/customer-archive/sync-kingdee',
method: 'post',
params: {
id,
},
});
};
export const recognizeBusinessLicenseOcr = imageUrl => {
return request({
url: '/blade-transport/baidu-ocr/recognize-url',
+8
View File
@@ -223,6 +223,14 @@ axios.interceptors.response.use(
},
error => {
NProgress.done();
// 网络级失败(HTTP 状态 >500、网关错误、超时、断网等 validateStatus 放行之外的响应)
// 原为静默 reject,接口报错页面无任何感知;统一补错误提示,取消请求与刷新令牌请求除外
if (!axios.isCancel(error) && !isRefreshTokenRequest(error?.config)) {
ElMessage({
message: error?.response?.data?.msg || '请求失败,请稍后重试',
type: 'error',
});
}
return Promise.reject(new Error(error));
}
);
@@ -497,6 +497,7 @@ const emptyLine = () => ({
localId: ++localId,
goodsCategory: '',
goodsName: '',
taxClassificationCode: '',
unit: '吨',
quantity: 0,
unitPriceNoTax: 0,
@@ -724,9 +725,11 @@ export default {
const item = this.findInvoiceItem(line);
if (item) {
line.taxRate = Number(item.defaultTaxRate || 0);
line.taxClassificationCode = item.taxClassificationCode || '';
} else if (clearInvalid) {
line.goodsName = '';
line.taxRate = 0;
line.taxClassificationCode = '';
}
this.recalculateLine(line);
},
@@ -734,6 +737,7 @@ export default {
const names = this.invoiceItemNames(line.goodsCategory);
if (!names.some(item => item.shortName === line.goodsName)) line.goodsName = '';
line.taxRate = 0;
line.taxClassificationCode = this.findInvoiceItem(line)?.taxClassificationCode || '';
if (names.length === 1) {
line.goodsName = names[0].shortName;
this.handleGoodsNameChange(line);
@@ -743,9 +747,11 @@ export default {
const item = this.findInvoiceItem(line);
if (!item) {
line.taxRate = 0;
line.taxClassificationCode = '';
return;
}
line.taxRate = Number(item.defaultTaxRate || 0);
line.taxClassificationCode = item.taxClassificationCode || '';
this.recalculateLine(line);
},
validateSettlements(rule, value, callback) {
@@ -1141,6 +1147,7 @@ export default {
lines: sheet.lines.map(line => ({
goodsCategory: line.goodsCategory,
goodsName: line.goodsName,
taxClassificationCode: line.taxClassificationCode,
unit: line.unit,
quantity: line.quantity,
unitPriceNoTax: line.unitPriceNoTax,
+23 -2
View File
@@ -84,6 +84,13 @@
@click="handleSync"
>批量同步</el-button
>
<el-button
v-if="hasPermission('payment_application_sync')"
type="primary"
plain
@click="handleSyncResult"
>同步付款结果</el-button
>
<el-button
v-if="hasPermission('payment_application_add')"
type="primary"
@@ -396,8 +403,22 @@ export default {
this.$message.warning('请选择至少一条审批通过的付款申请');
return;
}
await api.syncKingdeeBatch(rows.map(row => row.id));
this.$message.success(`已同步${rows.length}条付款申请`);
const data = this.unwrapData(await api.syncKingdeeBatch(rows.map(row => row.id))) || [];
const failedList = data.filter(item => String(item).includes('同步失败'));
if (failedList.length) {
this.$message({
type: 'warning',
message: `同步完成:成功${data.length - failedList.length}条,失败${failedList.length}条。${failedList.join('')}`,
duration: 8000,
});
} else {
this.$message.success(`已同步${data.length}条付款申请`);
}
this.loadTable();
},
async handleSyncResult() {
const data = this.unwrapData(await api.syncKingdeeResult());
this.$message.success(data || '金蝶付款结果回写完成');
this.loadTable();
},
handleExport() {
@@ -22,6 +22,7 @@
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择"
@change="fetchBfiExchangeRate"
/>
<el-select
v-else-if="field.type === 'project' && editable"
@@ -418,11 +419,69 @@
append-to-body
destroy-on-close
>
<el-form
:model="invoiceClaimDialog.query"
inline
label-position="right"
label-width="100px"
class="formal-editor__invoice-claim-search"
@submit.prevent
>
<el-form-item label="发票号码">
<el-input
v-model="invoiceClaimDialog.query.invoiceNo"
clearable
@keyup.enter="handleInvoiceClaimQuery"
/>
</el-form-item>
<el-form-item label="开票日期">
<el-date-picker
v-model="invoiceClaimDialog.query.invoiceDateRange"
type="daterange"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item label="销方税号">
<el-input
v-model="invoiceClaimDialog.query.salerTaxNo"
clearable
@keyup.enter="handleInvoiceClaimQuery"
/>
</el-form-item>
<el-form-item label="购方税号">
<el-input
v-model="invoiceClaimDialog.query.buyerTaxNo"
clearable
@keyup.enter="handleInvoiceClaimQuery"
/>
</el-form-item>
<el-form-item label="单据使用状态">
<el-select
v-model="invoiceClaimDialog.query.expenseStatus"
clearable
placeholder="请选择"
>
<el-option
v-for="item in expenseStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="resetInvoiceClaimQuery">重置</el-button>
<el-button type="primary" @click="handleInvoiceClaimQuery">查询</el-button>
</el-form-item>
</el-form>
<el-table
ref="invoiceClaimTable"
v-loading="invoiceClaimDialog.loading"
:data="invoiceClaimPagedRows"
row-key="invoiceNo"
:data="invoiceClaimDialog.rows"
row-key="serialNo"
border
height="520"
@selection-change="invoiceClaimDialog.selected = $event"
@@ -437,9 +496,9 @@
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column prop="invoiceNo" label="发票号" min-width="180" align="center" />
<el-table-column prop="invoiceDate" label="开票日期" min-width="130" align="center" />
<el-table-column prop="invoiceType" label="发票类型" min-width="150" align="center" />
<el-table-column prop="issuerName" label="开票单位" min-width="180" align="center" />
<el-table-column prop="receiverName" label="受票单位" min-width="180" align="center" />
<el-table-column prop="invoiceTypeName" label="发票类型" min-width="150" align="center" />
<el-table-column prop="salerName" label="开票单位" min-width="180" align="center" />
<el-table-column prop="buyerName" label="受票单位" min-width="180" align="center" />
<el-table-column
prop="invoiceAmount"
label="发票金额(含税)"
@@ -459,6 +518,8 @@
:total="invoiceClaimDialog.page.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="loadInvoiceClaims"
@size-change="handleInvoiceClaimSizeChange"
/>
</div>
<template #footer>
@@ -940,6 +1001,7 @@ import {
getNextNo,
getReceiptClaims,
claimInvoices as claimInvoicesApi,
queryInvoicePool,
save,
} from '@/api/settlement/formalSettlement';
import {
@@ -947,6 +1009,7 @@ import {
getDetailFees as getPreSettlementDetailFees,
} from '@/api/settlement/preSettlement';
import { calculateAdjustedFee, getFeeDetail } from '@/api/settlement/receivable-payable-detail';
import { getBfiExchangeRate } from '@/api/base/currency';
import {
createFormalSettlementForm,
formalSettlementFormFields,
@@ -1004,7 +1067,20 @@ export default {
rows: [],
selected: [],
page: { current: 1, size: 10, total: 0 },
query: {
invoiceNo: '',
invoiceDateRange: [],
salerTaxNo: '',
buyerTaxNo: '',
expenseStatus: '',
},
},
expenseStatusOptions: [
{ value: '1', label: '未用' },
{ value: '30', label: '在用' },
{ value: '60', label: '已用' },
{ value: '65', label: '已入账' },
],
attachmentUploadFiles: [],
attachmentTypeOptions: [],
attachmentFileTypes: [
@@ -1146,11 +1222,6 @@ export default {
});
return Array.from(names);
},
invoiceClaimPagedRows() {
const { current, size } = this.invoiceClaimDialog.page;
const start = (current - 1) * size;
return this.invoiceClaimDialog.rows.slice(start, start + size);
},
},
watch: {
modelValue: {
@@ -1231,6 +1302,13 @@ export default {
this.invoiceClaimDialog.visible = false;
this.invoiceClaimDialog.rows = [];
this.invoiceClaimDialog.selected = [];
this.invoiceClaimDialog.query = {
invoiceNo: '',
invoiceDateRange: [],
salerTaxNo: '',
buyerTaxNo: '',
expenseStatus: '',
};
this.attachmentUploadFiles = [];
this.billingRuleDialog = {
visible: false,
@@ -1250,6 +1328,7 @@ export default {
if (this.initialData) await this.applyInitialData();
Object.assign(this.form, newRecordAudit);
await this.refreshFormalSettlementNo();
await this.fetchBfiExchangeRate();
return;
}
this.loading = true;
@@ -1482,6 +1561,7 @@ export default {
(settlementType === 'receivable' ? contract.partyA : contract.partyB),
settlementType,
settlementTypeName: settlementType === 'receivable' ? '应收' : '应付',
currency: contract.settlementCurrency || 'RMB',
});
this.sources = [];
this.details = [];
@@ -1489,6 +1569,23 @@ export default {
this.form.sourcePreSettlementIds = [];
this.form.sourceDetailIds = [];
if (refreshSettlementNo) this.refreshFormalSettlementNo();
if (this.form.currency !== 'RMB') this.fetchBfiExchangeRate();
},
async fetchBfiExchangeRate() {
const currency = this.form.currency;
if (!currency || currency === 'RMB') return;
if (!this.form.exchangeRateDate) return;
try {
const { data } = await getBfiExchangeRate(currency, this.form.exchangeRateDate);
const rateData = data?.data;
if (rateData?.excval != null) {
this.form.exchangeRate = Number(rateData.excval);
} else {
this.$message.warning(`未查询到 ${currency} 的BFI汇率数据`);
}
} catch (e) {
this.$message.warning('查询BFI汇率失败,请手动输入结算汇率');
}
},
openCandidateDialog() {
this.candidate.visible = true;
@@ -2202,79 +2299,51 @@ export default {
this.adjust.saving = false;
}
},
claimInvoices() {
this.invoiceClaimLoading = true;
try {
const keyword = String(this.invoiceClaimNo || '').trim();
const defaultNo = `MOCK${this.$dayjs().format('YYYYMMDD')}`;
const baseNo = (keyword || defaultNo).slice(0, 29);
const numbers = [baseNo, `${baseNo}-02`, `${baseNo}-03`];
const invoiceAmounts = [1000, 2000, 3000];
const availableAmounts = [1000, 1000, 3000];
const preferredMatchedAmounts = [1000, 1000, 2000];
let remainingAmount = Math.max(
Number(this.form.settlementAmount || this.summaryTotal || 0) -
this.invoices.reduce((total, item) => total + Number(item.matchedAmount || 0), 0),
0
);
const existingNumbers = new Set(this.invoices.map(item => String(item.invoiceNo)));
const mockRows = numbers
.map((invoiceNo, index) => {
const matchedAmount = Math.min(
preferredMatchedAmounts[index],
availableAmounts[index],
remainingAmount
);
remainingAmount = Number((remainingAmount - matchedAmount).toFixed(2));
return {
invoiceNo,
invoiceDate: this.$dayjs().subtract(index, 'day').format('YYYY-MM-DD'),
invoiceType: index === 1 ? '增值税普通发票' : '增值税专用发票',
taxRate: [3, 6, 9][index],
invoiceAmount: invoiceAmounts[index],
availableInvoiceAmount: availableAmounts[index],
matchedAmount,
attachmentJson: '',
mockData: true,
};
})
.filter(item => !existingNumbers.has(item.invoiceNo));
if (!mockRows.length) {
this.$message.warning('当前发票号码的Mock数据已认领');
return;
}
this.invoices = [...this.invoices, ...mockRows];
this.$message.success(`已生成${mockRows.length}条Mock发票数据`);
} finally {
this.invoiceClaimLoading = false;
}
},
openInvoiceClaimDialog() {
const stamp = Date.now();
this.invoiceClaimDialog.loading = true;
this.invoiceClaimDialog.rows = Array.from({ length: 80 }, (_, index) => {
const amount = Math.floor(1000 + Math.random() * 49000);
return {
invoiceNo: `MOCK${stamp}${String(index + 1).padStart(3, '0')}`.slice(0, 32),
invoiceDate: this.$dayjs()
.subtract(Math.floor(Math.random() * 90), 'day')
.format('YYYY-MM-DD'),
invoiceType: index % 2 ? '增值税普通发票' : '增值税专用发票',
issuerName: this.form.payerName || 'Mock开票单位',
receiverName: this.form.payeeName || 'Mock受票单位',
invoiceAmount: amount,
availableInvoiceAmount: amount,
matchedAmount: amount,
taxRate: [3, 6, 9, 13][index % 4],
attachmentJson: '',
mockData: true,
};
});
this.invoiceClaimDialog.selected = [];
this.invoiceClaimDialog.page.current = 1;
this.invoiceClaimDialog.page.total = 80;
this.invoiceClaimDialog.query.invoiceNo = String(this.invoiceClaimNo || '').trim();
this.invoiceClaimDialog.visible = true;
this.handleInvoiceClaimQuery();
},
handleInvoiceClaimQuery() {
this.invoiceClaimDialog.page.current = 1;
this.loadInvoiceClaims();
},
resetInvoiceClaimQuery() {
this.invoiceClaimDialog.query = {
invoiceNo: '',
invoiceDateRange: [],
salerTaxNo: '',
buyerTaxNo: '',
expenseStatus: '',
};
this.handleInvoiceClaimQuery();
},
handleInvoiceClaimSizeChange() {
this.invoiceClaimDialog.page.current = 1;
this.loadInvoiceClaims();
},
async loadInvoiceClaims() {
this.invoiceClaimDialog.loading = true;
try {
const query = this.invoiceClaimDialog.query;
const range = query.invoiceDateRange || [];
const data = this.unwrapData(
await queryInvoicePool({
current: this.invoiceClaimDialog.page.current,
size: this.invoiceClaimDialog.page.size,
invoiceNo: query.invoiceNo || undefined,
invoiceDateStart: range[0] || undefined,
invoiceDateEnd: range[1] || undefined,
salerTaxNo: query.salerTaxNo || undefined,
buyerTaxNo: query.buyerTaxNo || undefined,
expenseStatus: query.expenseStatus || undefined,
})
);
this.invoiceClaimDialog.rows = data.records || [];
this.invoiceClaimDialog.page.total = Number(data.total || 0);
} finally {
this.invoiceClaimDialog.loading = false;
}
},
async confirmInvoiceClaims() {
if (!this.invoiceClaimDialog.selected.length) {
@@ -2300,7 +2369,11 @@ export default {
if (Number(this.form.settlementAmount || 0) > 0) {
remaining = Math.max(0, remaining - matchedAmount);
}
return { ...row, matchedAmount };
return {
...row,
invoiceType: row.invoiceTypeName || row.invoiceType || '',
matchedAmount,
};
})
.filter(row => row.matchedAmount > 0);
if (!invoices.length) {
@@ -2388,12 +2461,12 @@ export default {
},
viewInvoiceAttachment(row) {
const url = this.invoiceAttachmentUrl(row);
if (!url) return this.$message.warning('当前Mock发票暂无可查看附件');
if (!url) return this.$message.warning('当前发票暂无可查看附件');
window.open(url, '_blank');
},
downloadInvoiceAttachment(row) {
const url = this.invoiceAttachmentUrl(row);
if (!url) return this.$message.warning('当前Mock发票暂无可下载附件');
if (!url) return this.$message.warning('当前发票暂无可下载附件');
downloadFileByUrl(url, this.invoiceAttachmentName(row));
},
buildSavePayload() {
@@ -42,7 +42,7 @@
format="YYYY-MM-DD"
:disabled="!editable || form.currency === 'RMB'"
placeholder="请选择"
@change="recalculateLocalAmount"
@change="handleExchangeRateDateChange"
/>
<el-input-number
v-else-if="field.type === 'number'"
@@ -934,6 +934,7 @@ import {
submit,
} from '@/api/settlement/preSettlement';
import { calculateAdjustedFee, getFeeDetail } from '@/api/settlement/receivable-payable-detail';
import { getBfiExchangeRate } from '@/api/base/currency';
import { getDictionary } from '@/api/system/dictbiz';
import {
emptyPreSettlementForm,
@@ -1242,7 +1243,11 @@ export default {
await this.loadFeeCategoryOptions();
await this.loadTransportTypeOptions();
if (this.recordId) await this.loadDetail();
else if (this.initialData) await this.applyInitialData();
else if (this.initialData) {
await this.applyInitialData();
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
await this.fetchBfiExchangeRate();
}
},
async applyInitialData() {
const rows = Array.isArray(this.initialData?.rows) ? this.initialData.rows : [];
@@ -1527,7 +1532,12 @@ export default {
this.form.settlementType = contract.settlementType || 'payable';
this.form.payerName = contract.partyA;
this.form.payeeName = contract.partyB;
this.form.currency = contract.settlementCurrency || 'RMB';
this.summaryFees = [];
if (this.form.currency !== 'RMB') {
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
this.fetchBfiExchangeRate();
}
},
async saveDraft(shouldSubmit) {
await this.$refs.formRef?.validate();
@@ -1794,6 +1804,26 @@ export default {
.toFixed(2)
);
},
handleExchangeRateDateChange() {
this.fetchBfiExchangeRate();
this.recalculateLocalAmount();
},
async fetchBfiExchangeRate() {
if (!this.form.currency || this.form.currency === 'RMB') return;
if (!this.form.exchangeRateDate) return;
try {
const { data } = await getBfiExchangeRate(this.form.currency, this.form.exchangeRateDate);
const rateData = data?.data;
if (rateData?.excval != null) {
this.form.exchangeRate = Number(rateData.excval);
this.recalculateLocalAmount();
} else {
this.$message.warning(`未查询到 ${this.form.currency} 的BFI汇率数据`);
}
} catch (e) {
this.$message.warning('查询BFI汇率失败,请手动输入结算汇率');
}
},
recalculateLocalAmount() {
const rate = this.form.currency === 'RMB' ? 1 : Number(this.form.exchangeRate || 0);
this.form.localSettlementAmount = (Number(this.form.settlementAmount || 0) * rate).toFixed(2);
+196 -15
View File
@@ -109,6 +109,12 @@
{{ row.status === 1 ? '启用' : '停用' }}
</el-tag>
</template>
<template #kingdeeStatus="{ row }">
<el-tag v-if="row.kingdeeStatus === 'synced'" type="success">已推送</el-tag>
<el-tag v-else-if="row.kingdeeStatus === 'exist'" type="primary">金蝶已有</el-tag>
<el-tag v-else-if="row.kingdeeStatus === 'failed'" type="danger">推送失败</el-tag>
<el-tag v-else type="info">未推送</el-tag>
</template>
<template #menu="{ row }">
<el-link
type="primary"
@@ -165,6 +171,17 @@
>
{{ row.status === 1 ? '停用' : '启用' }}
</el-link>
<el-link
type="primary"
v-if="
hasPermission('customer_archive_sync') &&
row.approvalStatus === 'approved' &&
row.kingdeeStatus === 'failed'
"
@click="handleSyncKingdee(row)"
>
同步金蝶
</el-link>
<el-link
type="danger"
v-if="hasPermission('customer_archive_delete') && row.approvalStatus === 'draft'"
@@ -266,6 +283,7 @@
placeholder="请输入"
maxlength="18"
show-word-limit
:disabled="kingdeeLocked && Boolean(archiveForm.id)"
@input="
archiveForm.unifiedCreditCode = String(
archiveForm.unifiedCreditCode || ''
@@ -281,6 +299,7 @@
placeholder="请输入"
maxlength="100"
show-word-limit
:disabled="kingdeeLocked"
/>
</el-form-item>
</el-col>
@@ -304,12 +323,22 @@
</el-col>
<el-col :span="6">
<el-form-item label="企业法人" prop="legalPerson">
<el-input v-model="archiveForm.legalPerson" maxlength="10" show-word-limit />
<el-input
v-model="archiveForm.legalPerson"
maxlength="10"
show-word-limit
:disabled="kingdeeLocked"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="经营范围" prop="businessScope" required>
<el-select v-model="archiveForm.businessScope" multiple filterable clearable>
<el-select
v-model="archiveForm.businessScope"
multiple
filterable
clearable
>
<el-option
v-for="item in businessScopeOptions"
:key="item.value"
@@ -446,6 +475,7 @@
<el-input
v-model="archiveForm.registeredCapital"
maxlength="18"
:disabled="kingdeeLocked"
@input="value => handleDecimalInput('registeredCapital', value)"
/>
</el-form-item>
@@ -1492,6 +1522,8 @@ import {
remove,
getScoreTemplate,
recognizeBusinessLicenseOcr,
queryKingdeeCustomer,
syncKingdee,
} from '@/api/vehicle/customer-archive';
import {
getList as getCreditScoreQuantificationList,
@@ -1565,23 +1597,13 @@ export default {
const code = String(value || '')
.trim()
.toUpperCase();
const charset = '0123456789ABCDEFGHJKLMNPQRTUWXY';
const weights = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28];
if (!code) {
callback();
return;
}
if (code.length !== 18 || ![...code].every(char => charset.includes(char))) {
callback(new Error('统一社会信用代码必须为18位有效字符'));
return;
}
const sum = [...code.slice(0, 17)].reduce(
(total, char, index) => total + charset.indexOf(char) * weights[index],
0
);
const checkCode = charset[(31 - (sum % 31)) % 31];
if (code[17] !== checkCode) {
callback(new Error('统一社会信用代码校验码错误'));
const error = this.unifiedCreditCodeError(code);
if (error) {
callback(new Error(error));
return;
}
callback();
@@ -1621,6 +1643,8 @@ export default {
originalAccessType: '',
activeTab: 'scores',
archiveForm: this.emptyArchive(),
kingdeeBlocked: false,
kingdeeQueryTimer: null,
currentScore: { details: [] },
scoreRecordIndex: -1,
scoreAttachmentFiles: [],
@@ -1967,6 +1991,12 @@ export default {
valueFormat: 'YYYY-MM-DD HH:mm:ss',
minWidth: 170,
},
{
label: '金蝶同步状态',
prop: 'kingdeeStatus',
slot: true,
minWidth: 110,
},
{
label: '状态',
prop: 'status',
@@ -2004,6 +2034,9 @@ export default {
const authority = this.userInfo.authority || '';
return authority.includes('admin');
},
kingdeeLocked() {
return this.archiveForm.kingdeeStatus === 'exist';
},
permissionList() {
return {
addBtn: false,
@@ -2179,6 +2212,9 @@ export default {
},
},
watch: {
'archiveForm.unifiedCreditCode'(val) {
this.handleKingdeeCreditCodeChange(val);
},
'archiveForm.registeredDetailAddress'() {
this.$nextTick(() => this.checkOverflow('registeredDetailInput', 'registeredDetailOverflow'));
},
@@ -2190,6 +2226,119 @@ export default {
hasPermission(code) {
return this.isAdmin || this.validData(this.permission[code], false);
},
unifiedCreditCodeError(value) {
const code = String(value || '')
.trim()
.toUpperCase();
const charset = '0123456789ABCDEFGHJKLMNPQRTUWXY';
const weights = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28];
if (code.length !== 18 || ![...code].every(char => charset.includes(char))) {
return '统一社会信用代码必须为18位有效字符';
}
const sum = [...code.slice(0, 17)].reduce(
(total, char, index) => total + charset.indexOf(char) * weights[index],
0
);
const checkCode = charset[(31 - (sum % 31)) % 31];
if (code[17] !== checkCode) {
return '统一社会信用代码校验码错误';
}
return '';
},
resetKingdeeQueryState() {
this.kingdeeBlocked = false;
clearTimeout(this.kingdeeQueryTimer);
this.kingdeeQueryTimer = null;
},
// 新增态下重新输入统一社会信用代码后,此前金蝶回填的数据即失效,重置回填状态并允许对新代码重新查询
resetKingdeeBackfill() {
const form = this.archiveForm;
form.kingdeeStatus = 'none';
form.fullName = '';
form.legalPerson = '';
form.registeredCapital = '';
form.registeredDetailAddress = '';
form.businessScope = [];
form.businessTermType = '固定期限';
form.businessEndDate = '';
},
// 新增态下统一社会信用代码录入完成后自动查询金蝶(A902-1),防抖 500ms
// 代码一经变化即重新查询(含改回历史代码),不做同码去重
handleKingdeeCreditCodeChange(val) {
this.kingdeeBlocked = false;
if (this.readonly || this.archiveForm.id) return;
if (this.archiveForm.kingdeeStatus === 'exist') {
this.resetKingdeeBackfill();
}
const code = String(val || '')
.trim()
.toUpperCase();
if (code.length !== 18 || this.unifiedCreditCodeError(code)) return;
clearTimeout(this.kingdeeQueryTimer);
this.kingdeeQueryTimer = setTimeout(() => this.fetchKingdeeCustomer(code), 500);
},
async fetchKingdeeCustomer(code) {
let customer = null;
try {
const res = await queryKingdeeCustomer(code);
customer = res?.data?.data || null;
} catch (error) {
this.$message.warning('金蝶查询失败,可继续填写,审批通过后系统将自动复查');
return;
}
// 防止响应返回时用户已修改代码
if (String(this.archiveForm.unifiedCreditCode || '').trim().toUpperCase() !== code) return;
// 金蝶不存在(rows 为空,data 为 null 或空对象):静默返回,正常填写,不做提示
if (!customer || !customer.status) return;
if (customer.status === 'C') {
this.applyKingdeeCustomer(customer);
this.$alert(
'金蝶已创建该客户,将获取金蝶的工商信息,相关字段已自动回填并锁定。如信用代码有误,可直接修改,系统将重新查询。',
'提示',
{
confirmButtonText: '确定',
}
);
} else {
this.kingdeeBlocked = true;
this.$alert('金蝶审核中,请稍后重新添加。', '提示', { confirmButtonText: '确定' });
}
},
applyKingdeeCustomer(customer) {
const form = this.archiveForm;
form.kingdeeStatus = 'exist';
// 工商信息以金蝶为准,直接覆盖(后续字段锁定)
if (customer.name) form.fullName = customer.name;
if (customer.artificialperson) form.legalPerson = customer.artificialperson;
if (customer.bizpartnerAddress) form.registeredDetailAddress = customer.bizpartnerAddress;
if (customer.regcapital) {
const capital = String(customer.regcapital).replace(/,/g, '').match(/\d+(?:\.\d+)?/);
if (capital) form.registeredCapital = capital[0];
}
if (customer.businessterm) this.applyBusinessTermRecognition(customer.businessterm);
if (customer.businessscope) {
const matchedScopes = this.businessScopeOptions
.filter(
item =>
customer.businessscope.includes(item.label) ||
customer.businessscope.includes(item.value)
)
.map(item => item.value);
if (matchedScopes.length) form.businessScope = matchedScopes;
}
if (!form.shortName && customer.simplename) form.shortName = customer.simplename;
this.$nextTick(() => {
[
'unifiedCreditCode',
'fullName',
'legalPerson',
'registeredCapital',
'businessEndDate',
'businessScope',
'registeredDetailAddress',
].forEach(prop => this.$refs.archiveForm?.validateField(prop));
});
},
// 检测输入框文本是否溢出,仅溢出时才允许 tooltip 显示完整地址
checkOverflow(refName, flag) {
const inst = this.$refs[refName];
@@ -2219,6 +2368,9 @@ export default {
invoices: [],
scores: [],
changeRecords: [],
kingdeeStatus: 'none',
kingdeeFailReason: '',
kingdeeSyncTime: null,
};
},
getDetailList(type) {
@@ -3919,6 +4071,7 @@ export default {
}
this.archiveForm = this.emptyArchive();
this.originalAccessType = '';
this.resetKingdeeQueryState();
this.qualificationFiles = [];
this.qualificationUploadFiles = [];
this.ocrQualificationUploadFiles = [];
@@ -3927,6 +4080,7 @@ export default {
},
resetArchive() {
this.readonly = false;
this.resetKingdeeQueryState();
this.changeRecordDetailVisible = false;
this.changeRecordDetail = null;
this.changeRecordDetailRows = [];
@@ -4047,6 +4201,10 @@ export default {
return archive;
},
saveArchive() {
if (this.kingdeeBlocked) {
this.$message.warning('金蝶审核中,请稍后重新添加');
return;
}
this.$refs.archiveForm.validate(valid => {
if (!valid) {
this.$message.warning('请先完整填写必填项');
@@ -4060,6 +4218,10 @@ export default {
});
},
saveAndSubmitArchive() {
if (this.kingdeeBlocked) {
this.$message.warning('金蝶审核中,请稍后重新添加');
return;
}
this.$refs.archiveForm.validate(valid => {
if (!valid) {
this.$message.warning('请先完整填写必填项');
@@ -4707,6 +4869,25 @@ export default {
this.$message({ type: 'success', message: '审核通过!' });
});
},
handleSyncKingdee(row) {
this.$confirm('是否确认推送金蝶?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => syncKingdee(row.id))
.then(res => {
const archive = res?.data?.data || {};
if (archive.kingdeeStatus === 'synced') {
this.$message.success('推送成功,金蝶客户编码与客商编号一致');
} else if (archive.kingdeeStatus === 'exist') {
this.$message.info('金蝶已有该客商档案,无需推送');
} else {
this.$message.warning(`推送失败:${archive.kingdeeFailReason || '未知原因'}`);
}
this.onLoad(this.page);
});
},
handleReject(row) {
this.$confirm('是否确认审核不通过?', '提示', {
confirmButtonText: '确定',