✨ 新增项目预付核销 UI,并修复常用线路新增不显示与付款提交后刷新
结算单附件增加项目预付核销;常用线路超管默认查全部组织并按筛选归属新增;付款申请提交后关页并刷新列表;平台公司选项 ID 按字符串绑定。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,13 @@ export const getReferenceAmount = (paymentType, referenceId, excludeId) =>
|
||||
method: 'get',
|
||||
params: { paymentType, referenceId, excludeId: excludeId || undefined },
|
||||
});
|
||||
/** 按合同查询可选项目预付核销列表(审批通过且仍有可核销余额) */
|
||||
export const getProjectAdvanceWriteOffOptions = params =>
|
||||
request({
|
||||
url: `${baseUrl}/project-advance-write-off-options`,
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
|
||||
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
|
||||
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
|
||||
|
||||
@@ -396,8 +396,11 @@ export default {
|
||||
return this.selectionList.map(item => item.id).join(',');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.applyDefaultDeptSearch();
|
||||
created() {
|
||||
// 超管默认查全部组织,不预填组织筛选;普通用户预填当前组织。
|
||||
if (!this.isAdmin) {
|
||||
this.applyDefaultDeptSearch();
|
||||
}
|
||||
this.initDeptTree();
|
||||
},
|
||||
methods: {
|
||||
@@ -455,10 +458,23 @@ export default {
|
||||
normalizeListQuery(params = {}) {
|
||||
// 搜索条件优先于缓存的 query,避免默认组织 ID 覆盖用户新选择的组织。
|
||||
const query = { ...this.query, ...params };
|
||||
const rawDeptId = query.deptId ?? query.searchDeptId;
|
||||
// 本次搜索显式带了组织字段时,以本次为准(含清空);否则沿用缓存。
|
||||
const hasDeptInParams =
|
||||
Object.prototype.hasOwnProperty.call(params, 'deptId') ||
|
||||
Object.prototype.hasOwnProperty.call(params, 'searchDeptId');
|
||||
const rawDeptId = hasDeptInParams
|
||||
? params.deptId ?? params.searchDeptId
|
||||
: query.deptId ?? query.searchDeptId;
|
||||
delete query.searchDeptId;
|
||||
if (rawDeptId !== undefined && rawDeptId !== null && String(rawDeptId).trim() !== '') {
|
||||
query.deptId = String(rawDeptId).split(',')[0].trim();
|
||||
// 级联可能回传路径数组,统一取末级并保持字符串,避免雪花 ID 精度丢失。
|
||||
const deptValue = Array.isArray(rawDeptId) ? rawDeptId.at(-1) : rawDeptId;
|
||||
query.deptId = String(deptValue ?? '')
|
||||
.split(',')[0]
|
||||
.trim();
|
||||
if (!query.deptId) {
|
||||
delete query.deptId;
|
||||
}
|
||||
} else {
|
||||
delete query.deptId;
|
||||
}
|
||||
@@ -466,6 +482,23 @@ export default {
|
||||
return query;
|
||||
},
|
||||
applyDefaultDeptSearch() {
|
||||
// 超管默认不限组织,避免预填组织后新增落在其他组织时列表看不见。
|
||||
if (this.isAdmin) {
|
||||
const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
|
||||
if (deptColumn) {
|
||||
deptColumn.searchValue = undefined;
|
||||
}
|
||||
const nextQuery = { ...this.query };
|
||||
delete nextQuery.deptId;
|
||||
delete nextQuery.searchDeptId;
|
||||
this.query = nextQuery;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.crud?.searchForm) {
|
||||
this.$refs.crud.searchForm.searchDeptId = undefined;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
const deptId = this.defaultDeptId();
|
||||
const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
|
||||
if (!deptId || !deptColumn) return;
|
||||
@@ -477,6 +510,11 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
resolveDeptName(deptId) {
|
||||
if (!deptId) return '';
|
||||
const matched = this.deptOptions.find(item => String(item.value) === String(deptId));
|
||||
return matched ? String(matched.label || '').trim() : '';
|
||||
},
|
||||
resetAddressQuery() {
|
||||
this.addressQuery = {
|
||||
addressName: '',
|
||||
@@ -933,7 +971,8 @@ export default {
|
||||
}
|
||||
api.submit(submitRow).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, this.query);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
@@ -951,7 +990,7 @@ export default {
|
||||
}
|
||||
api.submit(submitRow).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.onLoad(this.page, this.query);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
@@ -1006,7 +1045,13 @@ export default {
|
||||
beforeOpen(done, type) {
|
||||
this.dialogReadonly = type === 'view';
|
||||
if (type === 'add') {
|
||||
this.form = {};
|
||||
const form = {};
|
||||
// 超管若当前按某组织筛选,新增默认归属该组织,避免保存后被筛选条件滤掉。
|
||||
if (this.isAdmin && this.query.deptId) {
|
||||
form.deptId = String(this.query.deptId);
|
||||
form.deptName = this.resolveDeptName(form.deptId);
|
||||
}
|
||||
this.form = form;
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2506,8 +2506,9 @@ export default {
|
||||
const list = res.data.data || [];
|
||||
this.platformCompanyOptions = list.map(item => ({
|
||||
label: item.deptName || item.fullName || item.title || item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
// 雪花 ID 必须以字符串绑定,避免 JS Number 精度丢失导致多选提交异常
|
||||
value: item.id == null || item.id === '' ? '' : String(item.id),
|
||||
})).filter(item => item.value);
|
||||
this.ensureCurrentPlatformOption();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -688,6 +688,9 @@ const attachmentViewerPlugins = [
|
||||
fallbackPlugin(),
|
||||
];
|
||||
|
||||
const paymentApplicationListRoute = '/payment/payment-application';
|
||||
const paymentApplicationListRefreshKey = 'payment-application-list-refresh';
|
||||
|
||||
const ATTACHMENT_MATERIALS = {
|
||||
weighingSlip: { key: 'weighingSlip', label: '磅单', keywords: ['磅单'] },
|
||||
entrustOrder: { key: 'entrustOrder', label: '委托单', keywords: ['委托单'] },
|
||||
@@ -2712,12 +2715,14 @@ export default {
|
||||
mkEnabled: await loadMkEnabled().catch(() => false),
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
// 通知列表页(keep-alive)重新拉取数据
|
||||
sessionStorage.setItem(paymentApplicationListRefreshKey, '1');
|
||||
this.goBack();
|
||||
},
|
||||
goBack() {
|
||||
removeSettlementTransfer(this.$route.query.transferToken);
|
||||
this.$router.$avueRouter?.closeTag?.();
|
||||
this.$router.push('/payment/payment-application');
|
||||
this.$router.push(paymentApplicationListRoute);
|
||||
},
|
||||
formatMoney(value) {
|
||||
const amount = Number(value);
|
||||
|
||||
@@ -152,6 +152,9 @@ import organizationSearch from '@/mixins/organization-search';
|
||||
import { getMKApprovalUrl } from '@/api/system/business-process';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
|
||||
const paymentApplicationListRoute = '/payment/payment-application';
|
||||
const paymentApplicationListRefreshKey = 'payment-application-list-refresh';
|
||||
|
||||
export default {
|
||||
name: 'PaymentApplication',
|
||||
mixins: [organizationSearch],
|
||||
@@ -193,7 +196,28 @@ export default {
|
||||
this.mkEnabled = false;
|
||||
});
|
||||
},
|
||||
activated() {
|
||||
this.consumeListRefreshFlag();
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
if (
|
||||
to.path === paymentApplicationListRoute &&
|
||||
from?.path?.includes('/payment-application/form')
|
||||
) {
|
||||
this.consumeListRefreshFlag();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/** 表单提交成功后带标记返回时,重新拉取列表 */
|
||||
consumeListRefreshFlag() {
|
||||
if (!sessionStorage.getItem(paymentApplicationListRefreshKey)) {
|
||||
return;
|
||||
}
|
||||
sessionStorage.removeItem(paymentApplicationListRefreshKey);
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
hasPermission(code) {
|
||||
return this.permission?.[code] !== false;
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user