Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a554ccf38e | |||
| a748e52551 | |||
| e89e78b527 | |||
| 2a03991297 | |||
| 58aaf79445 | |||
| f067518c0e | |||
| 11460f3378 | |||
| 7f543937fb |
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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' },
|
||||
];
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.invoiceClaimDialog.loading = false;
|
||||
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);
|
||||
|
||||
@@ -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: '确定',
|
||||
|
||||
Reference in New Issue
Block a user