✨ 新增项目预付核销 UI,并修复常用线路新增不显示与付款提交后刷新

结算单附件增加项目预付核销;常用线路超管默认查全部组织并按筛选归属新增;付款申请提交后关页并刷新列表;平台公司选项 ID 按字符串绑定。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-16 02:02:00 +08:00
parent 0b170fe5c1
commit 4b01316f64
8 changed files with 447 additions and 12 deletions
@@ -378,6 +378,16 @@
</div>
</section-card>
<project-advance-write-off
ref="projectAdvanceWriteOffRef"
v-model="projectAdvanceWriteOffs"
:editable="editable"
:contract-id="form.contractId"
:settlement-amount="form.settlementAmount"
settlement-type="formal"
:settlement-id="form.id"
/>
<section-card title="发票信息">
<div class="formal-editor__invoice-claim">
<el-form inline label-position="right" label-width="auto" @submit.prevent>
@@ -1011,12 +1021,13 @@ import {
viewKingdeeInvoiceFile,
} from '@/utils/kingdee-attachment';
import KingdeeInvoicePoolDialog from '@/components/kingdee-invoice-pool-dialog/main.vue';
import ProjectAdvanceWriteOff from './project-advance-write-off.vue';
import * as XLSX from 'xlsx';
import { isAdministrator } from '@/utils/auth';
export default {
name: 'FormalSettlementEditor',
components: { InfoFilled, KingdeeInvoicePoolDialog },
components: { InfoFilled, KingdeeInvoicePoolDialog, ProjectAdvanceWriteOff },
props: {
modelValue: Boolean,
recordId: [String, Number],
@@ -1045,6 +1056,7 @@ export default {
receiptClaims: [],
adjustments: [],
changeRecords: [],
projectAdvanceWriteOffs: [],
attachments: [],
selectedAttachmentRows: [],
invoices: [],
@@ -1275,6 +1287,13 @@ export default {
}
this.adjustments = data.adjustments || [];
this.changeRecords = data.changeRecords || [];
this.projectAdvanceWriteOffs = (data.projectAdvanceWriteOffs || []).map(row => ({
...row,
writeOffAmount: Number(row.writeOffAmount || 0),
projectAdvanceAmount: Number(row.projectAdvanceAmount || 0),
availableWriteOffAmount: Number(row.availableWriteOffAmount || 0),
createTime: row.paymentCreateTime || row.createTime,
}));
this.attachments = this.parseAttachments(data.attachmentsJson);
this.selectedAttachmentRows = [];
this.invoices = data.invoices || [];
@@ -1305,6 +1324,7 @@ export default {
this.receiptClaims = [];
this.adjustments = [];
this.changeRecords = [];
this.projectAdvanceWriteOffs = [];
this.attachments = [];
this.selectedAttachmentRows = [];
this.invoices = [];
@@ -2549,6 +2569,7 @@ export default {
matchedAmount: Number(row.matchedAmount || 0),
attachmentJson: row.attachmentJson || '',
})),
projectAdvanceWriteOffs: this.$refs.projectAdvanceWriteOffRef?.toSavePayload?.() || [],
remark: this.form.remark,
};
},
@@ -2557,6 +2578,9 @@ export default {
},
async handleDraftSave() {
// 保存草稿:不做表单校验,直接落草稿并返回列表刷新
if (this.$refs.projectAdvanceWriteOffRef?.validateBeforeSave?.() === false) {
return;
}
this.draftSaving = true;
try {
await save({
@@ -2584,6 +2608,9 @@ export default {
return this.$message.warning('请完善手工费用的费用类型和费用项');
}
if (!this.validateInvoices()) return;
if (this.$refs.projectAdvanceWriteOffRef?.validateBeforeSave?.() === false) {
return;
}
// 附件类型仅做提示,不阻断正式结算单新增/保存提交。
// this.validateAttachments();
this.saving = true;
@@ -421,6 +421,16 @@
</div>
</section-card>
<project-advance-write-off
ref="projectAdvanceWriteOffRef"
v-model="projectAdvanceWriteOffs"
:editable="editable"
:contract-id="form.contractId"
:settlement-amount="form.settlementAmount"
settlement-type="pre"
:settlement-id="form.id"
/>
<section-card v-if="form.id && readonly" title="预付信息">
<el-table :data="advances" border>
<el-table-column type="index" label="序号" width="64" align="center" />
@@ -939,6 +949,7 @@ import { h } from 'vue';
import { mapGetters } from 'vuex';
import { InfoFilled } from '@element-plus/icons-vue';
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
import ProjectAdvanceWriteOff from './project-advance-write-off.vue';
import { getMkPublicDetail } from '@/api/mk-process';
import {
adjustDetail,
@@ -981,7 +992,7 @@ import { isAdministrator } from '@/utils/auth';
export default {
name: 'PreSettlementEditor',
components: { InfoFilled, ChangeRecordDetailDialog },
components: { InfoFilled, ChangeRecordDetailDialog, ProjectAdvanceWriteOff },
props: {
modelValue: {
type: Boolean,
@@ -1084,6 +1095,7 @@ export default {
size: 10,
},
advances: [],
projectAdvanceWriteOffs: [],
changeRecords: [],
attachments: [],
attachmentTypeOptions: [],
@@ -1396,6 +1408,7 @@ export default {
this.appliedDetailQuery = { ...this.detailQuery };
this.detailPage = { current: 1, size: 10 };
this.advances = [];
this.projectAdvanceWriteOffs = [];
this.changeRecords = [];
this.attachments = [];
this.selectedAttachmentRows = [];
@@ -1435,6 +1448,13 @@ export default {
createUserName: row.createUserName || detail.createUserName,
createTime: row.createTime || detail.createTime,
}));
this.projectAdvanceWriteOffs = (detail.projectAdvanceWriteOffs || []).map(row => ({
...row,
writeOffAmount: Number(row.writeOffAmount || 0),
projectAdvanceAmount: Number(row.projectAdvanceAmount || 0),
availableWriteOffAmount: Number(row.availableWriteOffAmount || 0),
createTime: row.paymentCreateTime || row.createTime,
}));
this.changeRecords = detail.changeRecords || [];
this.attachments = this.parseAttachments(detail.attachmentsJson);
this.sortAttachments();
@@ -1579,6 +1599,7 @@ export default {
this.form.payerName = '';
this.form.payeeName = '';
this.summaryFees = [];
this.projectAdvanceWriteOffs = [];
return;
}
this.form.contractId = contract.id;
@@ -1593,6 +1614,7 @@ export default {
this.form.payeeName = contract.partyB;
this.form.currency = contract.settlementCurrency || 'RMB';
this.summaryFees = [];
this.projectAdvanceWriteOffs = [];
if (this.form.currency !== 'RMB') {
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
this.fetchBfiExchangeRate();
@@ -1613,6 +1635,9 @@ export default {
this.$message.warning(`请完善结算合计第${invalidManualFeeIndex + 1}行的费用类型和费用项`);
return;
}
if (this.$refs.projectAdvanceWriteOffRef?.validateBeforeSave?.() === false) {
return;
}
const stateKey = shouldSubmit ? 'submitting' : 'saving';
this[stateKey] = true;
try {
@@ -1660,6 +1685,7 @@ export default {
remark: row.remark,
manualFlag: row.manualFlag || 0,
})),
projectAdvanceWriteOffs: this.$refs.projectAdvanceWriteOffRef?.toSavePayload?.() || [],
};
},
addSummaryFee() {
@@ -0,0 +1,300 @@
<template>
<section-card title="项目预付核销">
<template v-if="editable" #extra>
<el-button type="primary" :disabled="!contractId" @click="openSelectDialog">选择</el-button>
</template>
<el-table :data="rows" border>
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column
prop="paymentNo"
label="预付单号"
min-width="160"
align="center"
show-overflow-tooltip
/>
<el-table-column label="项目预付金额" min-width="140" align="center">
<template #default="{ row }">{{ formatMoney(row.projectAdvanceAmount) }}</template>
</el-table-column>
<el-table-column label="可核销金额" min-width="140" align="center">
<template #default="{ row }">{{ formatMoney(row.availableWriteOffAmount) }}</template>
</el-table-column>
<el-table-column label="本次核销金额" min-width="160" align="center">
<template #default="{ row }">
<el-input-number
v-if="editable"
v-model="row.writeOffAmount"
:min="0"
:precision="2"
:controls="false"
class="project-advance-write-off__amount"
@change="handleAmountChange(row)"
/>
<span v-else>{{ formatMoney(row.writeOffAmount) }}</span>
</template>
</el-table-column>
<el-table-column
prop="kingdeeBillNo"
label="金蝶预付单号"
min-width="160"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.kingdeeBillNo || '-' }}</template>
</el-table-column>
<el-table-column
prop="createUserName"
label="创建人"
min-width="120"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.createUserName || '-' }}</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center">
<template #default="{ row }">{{ formatDateTime(row.createTime || row.paymentCreateTime) }}</template>
</el-table-column>
<el-table-column v-if="editable" label="操作" width="90" align="center" fixed="right">
<template #default="{ $index }">
<el-link type="danger" @click="removeRow($index)">删除</el-link>
</template>
</el-table-column>
</el-table>
<el-dialog
v-model="dialog.visible"
title="选择项目预付单"
width="960px"
append-to-body
destroy-on-close
class="dialog-form-title"
@closed="handleDialogClosed"
>
<el-table
v-loading="dialog.loading"
:data="dialog.rows"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" :selectable="rowSelectable" />
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column
prop="paymentNo"
label="预付单号"
min-width="160"
align="center"
show-overflow-tooltip
/>
<el-table-column label="项目预付金额" min-width="140" align="center">
<template #default="{ row }">{{ formatMoney(row.projectAdvanceAmount) }}</template>
</el-table-column>
<el-table-column label="可核销金额" min-width="140" align="center">
<template #default="{ row }">{{ formatMoney(row.availableWriteOffAmount) }}</template>
</el-table-column>
<el-table-column
prop="kingdeeBillNo"
label="金蝶预付单号"
min-width="160"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.kingdeeBillNo || '-' }}</template>
</el-table-column>
<el-table-column
prop="createUserName"
label="创建人"
min-width="120"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.createUserName || '-' }}</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center">
<template #default="{ row }">{{ formatDateTime(row.createTime) }}</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="dialog.visible = false">取消</el-button>
<el-button type="primary" :disabled="!dialog.selection.length" @click="confirmSelect">
确定
</el-button>
</template>
</el-dialog>
</section-card>
</template>
<script>
import { getProjectAdvanceWriteOffOptions } from '@/api/payment/paymentApplication';
export default {
name: 'ProjectAdvanceWriteOff',
props: {
modelValue: {
type: Array,
default: () => [],
},
editable: {
type: Boolean,
default: true,
},
contractId: {
type: [String, Number],
default: '',
},
settlementAmount: {
type: [String, Number],
default: 0,
},
settlementType: {
type: String,
required: true,
},
settlementId: {
type: [String, Number],
default: '',
},
},
emits: ['update:modelValue'],
data() {
return {
dialog: {
visible: false,
loading: false,
rows: [],
selection: [],
},
};
},
computed: {
rows: {
get() {
return Array.isArray(this.modelValue) ? this.modelValue : [];
},
set(value) {
this.$emit('update:modelValue', value);
},
},
},
methods: {
formatMoney(value) {
const amount = Number(value);
return Number.isFinite(amount) ? amount.toFixed(2) : '0.00';
},
formatDateTime(value) {
if (!value) return '-';
return this.$dayjs(value).format('YYYY-MM-DD HH:mm:ss');
},
money(value) {
const amount = Number(value);
return Number.isFinite(amount) ? Number(amount.toFixed(2)) : 0;
},
openSelectDialog() {
if (!this.contractId) {
this.$message.warning('请先选择合同');
return;
}
this.dialog.visible = true;
this.loadOptions();
},
async loadOptions() {
this.dialog.loading = true;
try {
const res = await getProjectAdvanceWriteOffOptions({
contractId: this.contractId,
excludeSettlementType: this.settlementType,
excludeSettlementId: this.settlementId || undefined,
});
this.dialog.rows = res?.data?.data || [];
} finally {
this.dialog.loading = false;
}
},
rowSelectable(row) {
const selectedIds = new Set(this.rows.map(item => String(item.paymentApplicationId)));
return !selectedIds.has(String(row.paymentApplicationId));
},
handleSelectionChange(list) {
this.dialog.selection = list || [];
},
confirmSelect() {
const nextRows = [...this.rows];
const existed = new Set(nextRows.map(item => String(item.paymentApplicationId)));
this.dialog.selection.forEach(item => {
const id = String(item.paymentApplicationId);
if (existed.has(id)) return;
nextRows.push({
paymentApplicationId: item.paymentApplicationId,
paymentNo: item.paymentNo,
projectAdvanceAmount: this.money(item.projectAdvanceAmount),
availableWriteOffAmount: this.money(item.availableWriteOffAmount),
writeOffAmount: this.money(item.availableWriteOffAmount),
kingdeeBillNo: item.kingdeeBillNo || '',
createUserName: item.createUserName || '',
createTime: item.createTime,
paymentCreateTime: item.createTime,
});
});
this.rows = nextRows;
this.dialog.visible = false;
this.validateTotal();
},
handleDialogClosed() {
this.dialog.rows = [];
this.dialog.selection = [];
},
handleAmountChange(row) {
const available = this.money(row.availableWriteOffAmount);
let amount = this.money(row.writeOffAmount);
if (amount < 0) amount = 0;
if (amount > available) {
amount = available;
this.$message.warning('本次核销金额不能超过可核销金额');
}
row.writeOffAmount = amount;
this.validateTotal();
},
validateTotal() {
const total = this.rows.reduce((sum, row) => sum + this.money(row.writeOffAmount), 0);
const settlementAmount = this.money(this.settlementAmount);
if (settlementAmount > 0 && total > settlementAmount) {
this.$message.warning('项目预付核销合计不能超过结算单金额');
return false;
}
return true;
},
validateBeforeSave() {
for (const row of this.rows) {
const amount = this.money(row.writeOffAmount);
if (amount <= 0) {
this.$message.warning(`预付单${row.paymentNo || ''}本次核销金额必须大于0`);
return false;
}
if (amount > this.money(row.availableWriteOffAmount)) {
this.$message.warning(`预付单${row.paymentNo || ''}本次核销金额不能超过可核销金额`);
return false;
}
}
return this.validateTotal();
},
removeRow(index) {
const nextRows = [...this.rows];
nextRows.splice(index, 1);
this.rows = nextRows;
},
toSavePayload() {
return this.rows.map(row => ({
paymentApplicationId: row.paymentApplicationId,
writeOffAmount: this.money(row.writeOffAmount),
}));
},
},
};
</script>
<style scoped lang="scss">
.project-advance-write-off__amount {
width: 100%;
}
:deep(.project-advance-write-off__amount .el-input__inner) {
text-align: right;
}
</style>