🐛 修复预付驳回重提、预结算变更记录与收票多选结算单

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-20 01:50:54 +08:00
parent 31794bf26a
commit 4c9bbf1eb7
8 changed files with 397 additions and 134 deletions
+117 -43
View File
@@ -44,7 +44,8 @@
reserve-keyword
:remote-method="searchCustomers"
:loading="customerLoading"
placeholder="请选择客商基础档案"
placeholder="请选择"
@change="syncFeeBearer"
>
<el-option
v-for="item in customerOptions"
@@ -63,6 +64,7 @@
allow-create
default-first-option
placeholder="请选择或输入"
@change="syncFeeBearer"
>
<el-option
v-for="item in receiverOptions"
@@ -74,7 +76,7 @@
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="票面金额" prop="faceAmount">
<el-form-item label="票面金额" prop="faceAmount" required>
<el-input-number
v-model="form.faceAmount"
:min="0"
@@ -111,17 +113,15 @@
</el-col>
<el-col :span="6">
<el-form-item label="可用部门" prop="availableDeptIds">
<el-tree-select
v-model="form.availableDeptIds"
:data="deptTree"
:props="deptProps"
node-key="id"
multiple
show-checkbox
<el-cascader
v-model="availableDeptCascaderValue"
:options="businessDeptTreeOptions"
:props="deptCascaderProps"
placeholder="请选择"
filterable
clearable
collapse-tags
collapse-tags-tooltip
placeholder="请选择,默认全部"
@change="handleDeptChange"
/>
</el-form-item>
</el-col>
@@ -140,17 +140,9 @@
</el-col>
<el-col :span="6">
<el-form-item label="费用承担方" prop="feeBearerId">
<el-select
v-model="form.feeBearerId"
filterable
remote
reserve-keyword
:remote-method="searchCustomers"
:loading="customerLoading"
placeholder="请选择客商基础档案"
>
<el-select v-model="form.feeBearerId" filterable clearable placeholder="请选择">
<el-option
v-for="item in customerOptions"
v-for="item in feeBearerOptions"
:key="item.id"
:label="customerName(item)"
:value="item.id"
@@ -245,13 +237,13 @@ const emptyForm = () => ({
billNo: '',
issuerId: null,
receiverName: '',
billType: 'issued',
billType: '',
faceAmount: null,
availableBalance: 0,
issueDate: '',
maturityDate: '',
availableDeptIds: ['all'],
availableDeptNames: '全部',
availableDeptIds: [],
availableDeptNames: '',
feeBearerId: null,
confirmedDiscountRate: null,
issuingBank: '',
@@ -272,9 +264,17 @@ export default {
receiverOptions: [],
customerLoading: false,
saving: false,
deptTree: [],
businessDeptTreeOptions: [],
deptMap: new Map(),
deptProps: { label: 'title', value: 'id', children: 'children' },
deptCascaderProps: {
label: 'label',
value: 'value',
children: 'children',
multiple: true,
checkStrictly: true,
emitPath: true,
expandTrigger: 'click',
},
bankOptions: [
'中国工商银行',
'中国农业银行',
@@ -309,7 +309,10 @@ export default {
issuerId: [{ required: true, message: '请选择出票单位', trigger: 'change' }],
receiverName: [{ required: true, message: '请选择或输入收票单位', trigger: 'change' }],
billType: [{ required: true, message: '请选择汇票类型', trigger: 'change' }],
faceAmount: [{ validator: this.validateFaceAmount, trigger: 'change' }],
faceAmount: [
{ required: true, message: '请输入票面金额', trigger: 'change' },
{ validator: this.validateFaceAmount, trigger: 'change' },
],
issueDate: [{ required: true, message: '请选择出票日期', trigger: 'change' }],
maturityDate: [{ required: true, validator: this.validateMaturityDate, trigger: 'change' }],
availableDeptIds: [{ validator: this.validateDepartments, trigger: 'change' }],
@@ -340,6 +343,42 @@ export default {
(Number(this.form.faceAmount || 0) * Number(this.form.confirmedDiscountRate || 0)) / 100
);
},
// 可用部门:表单存部门 id,级联多选回显用根到节点的路径
availableDeptCascaderValue: {
get() {
return (this.form.availableDeptIds || [])
.filter(id => id !== undefined && id !== null && String(id) !== 'all' && String(id) !== '')
.map(id => this.findDeptPath(id, this.businessDeptTreeOptions) || [String(id)]);
},
set(paths) {
const ids = [
...new Set(
(paths || [])
.map(path => {
const nodes = Array.isArray(path) ? path.filter(item => item !== undefined && item !== null && item !== '') : [];
return nodes.length ? String(nodes.at(-1)) : '';
})
.filter(Boolean)
),
];
this.form.availableDeptIds = ids;
this.form.availableDeptNames = ids
.map(id => this.deptMap.get(String(id)))
.filter(Boolean)
.join('、');
},
},
feeBearerOptions() {
const issuer = this.customerOptions.find(item => String(item.id) === String(this.form.issuerId));
const receiverName = String(this.form.receiverName || '').trim();
const receiver = receiverName
? this.customerOptions.find(item => this.customerName(item) === receiverName)
: null;
return [issuer, receiver].filter(
(item, index, rows) =>
item && rows.findIndex(row => row && String(row.id) === String(item.id)) === index
);
},
},
created() {
this.initialize();
@@ -352,7 +391,6 @@ export default {
async initialize() {
await Promise.all([this.loadDepartments(), this.searchCustomers('')]);
if (!this.recordId) {
this.form.issueDate = this.$dayjs().format('YYYY-MM-DD');
return;
}
const data = this.unwrapData(await api.getDetail(this.recordId));
@@ -361,14 +399,20 @@ export default {
...data,
confirmedDiscountRate: this.normalizeDiscountRate(data.confirmedDiscountRate),
bankDiscountReferenceRate: this.normalizeDiscountRate(data.bankDiscountReferenceRate),
availableDeptIds: this.parse(data.availableDeptIdsJson),
availableDeptIds: this.parse(data.availableDeptIdsJson)
.filter(id => String(id) !== 'all')
.map(id => String(id)),
attachments: this.parse(data.attachmentsJson),
};
this.usageRecords = data.usageRecords || [];
this.receiverOptions = data.receiverName ? [data.receiverName] : [];
this.ensureCustomerOption(data.issuerId, data.issuerName);
this.ensureCustomerOption(data.feeBearerId, data.feeBearerName);
this.handleDeptChange(this.form.availableDeptIds);
this.form.availableDeptNames = (this.form.availableDeptIds || [])
.map(id => this.deptMap.get(String(id)))
.filter(Boolean)
.join('、');
this.syncFeeBearer();
},
parse(value) {
if (!value) return [];
@@ -381,22 +425,52 @@ export default {
},
async loadDepartments() {
const response = await getDeptTree(this.userInfo?.tenantId);
const rows = this.unwrapData(response) || [];
this.deptTree = [{ id: 'all', title: '全部', children: rows }];
this.deptMap = new Map([['all', '全部']]);
this.flattenDepartments(rows).forEach(item => this.deptMap.set(String(item.id), item.title));
const rows = this.excludeExternalOrganization(this.unwrapData(response) || []);
this.businessDeptTreeOptions = this.toDeptCascaderOptions(rows);
this.deptMap = new Map();
this.flattenDepartments(rows).forEach(item =>
this.deptMap.set(String(item.id), item.title || item.deptName || item.name)
);
},
excludeExternalOrganization(list = []) {
return list.reduce((result, item) => {
const deptName = item.title || item.deptName || item.name;
if (String(deptName || '').trim() === '外部组织') return result;
result.push({
...item,
children: item.children?.length
? this.excludeExternalOrganization(item.children)
: item.children,
});
return result;
}, []);
},
toDeptCascaderOptions(list = []) {
return list.map(item => ({
label: item.title || item.deptName || item.name,
value: item.id == null || item.id === '' ? '' : String(item.id),
children: item.children?.length ? this.toDeptCascaderOptions(item.children) : undefined,
}));
},
flattenDepartments(rows) {
return (rows || []).flatMap(item => [item, ...this.flattenDepartments(item.children || [])]);
},
handleDeptChange(values) {
const ids = values || [];
this.form.availableDeptNames = ids.some(item => String(item) === 'all')
? '全部'
: ids
.map(item => this.deptMap.get(String(item)))
.filter(Boolean)
.join('、');
findDeptPath(id, tree = [], parents = []) {
const target = Array.isArray(id) ? id.at(-1) : id;
if (target === undefined || target === null || target === '') return null;
for (const node of tree || []) {
const path = [...parents, node.value];
if (String(node.value) === String(target)) return path;
const matched = this.findDeptPath(target, node.children, path);
if (matched) return matched;
}
return null;
},
syncFeeBearer() {
const allowed = this.feeBearerOptions.map(item => String(item.id));
if (this.form.feeBearerId && !allowed.includes(String(this.form.feeBearerId))) {
this.form.feeBearerId = null;
}
},
async searchCustomers(keyword) {
this.customerLoading = true;
@@ -545,7 +619,7 @@ export default {
.bill-ledger-form-page__form :deep(.el-select),
.bill-ledger-form-page__form :deep(.el-input-number),
.bill-ledger-form-page__form :deep(.el-date-editor),
.bill-ledger-form-page__form :deep(.el-tree-select) {
.bill-ledger-form-page__form :deep(.el-cascader) {
width: 100%;
}
.bill-ledger-form-page__form :deep(.el-input-number .el-input__inner) {