This commit is contained in:
2026-08-31 10:38:21 +08:00
parent a10eea247f
commit a75ac1cfa4
13 changed files with 864 additions and 161 deletions
@@ -60,7 +60,7 @@
v-else-if="field.type === 'number' && editable"
v-model="form[field.prop]"
:min="0.000001"
:precision="6"
:precision="2"
:controls="false"
/>
<span v-else-if="field.money">{{ formatMoney(form[field.prop]) }}</span>
@@ -153,8 +153,7 @@
<span
v-else-if="isSummaryMoney(column.prop)"
:class="{
'formal-editor__negative-amount':
column.prop === 'adjustAmount' && Number(row[column.prop] || 0) < 0,
'formal-editor__negative-amount': isNegativeAdjustedAmount(row, column.prop),
}"
>
{{ formatMoney(row[column.prop]) }}
@@ -224,7 +223,10 @@
<template #default="{ row }">
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
<span v-else-if="column.prop === 'transportQuantity'">
{{ formatQuantity(row[column.prop]) }}
{{ formatDetailTransportQuantity(row[column.prop]) }}
</span>
<span v-else-if="column.prop === 'mileage'">
{{ formatDetailMileage(row[column.prop]) }}
</span>
<span v-else-if="column.prop === 'transportType'">
{{ transportTypeName(row[column.prop]) }}
@@ -235,11 +237,9 @@
<el-table-column v-if="editable" label="操作" width="150" fixed="right" align="center">
<template #default="{ row }">
<div class="formal-editor__links">
<el-link v-if="row.formalSettlementId" type="primary" @click="openAdjustDialog(row)"
>调整</el-link
>
<el-link type="primary" @click="openAdjustDialog(row)">调整</el-link>
<el-link v-if="!row.sourcePreSettlementId" type="danger" @click="removeDetail(row)"
>删除</el-link
>踢出</el-link
>
</div>
</template>
@@ -254,7 +254,13 @@
{{ missingAttachmentTypeText }}
</span>
</template>
<el-table :data="attachments" border>
<div class="formal-editor__attachment-actions">
<el-button type="primary" :disabled="!attachments.length" @click="downloadAttachments">
批量下载
</el-button>
</div>
<el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column label="类型" min-width="180" align="center">
<template #default="{ row }">
@@ -323,7 +329,7 @@
</section-card>
<section-card title="发票信息">
<div v-if="editable" class="formal-editor__invoice-claim">
<div class="formal-editor__invoice-claim">
<el-form inline label-position="right" label-width="auto" @submit.prevent>
<el-form-item label="发票号码">
<el-input
@@ -394,9 +400,7 @@
<div class="formal-editor__links">
<el-link type="primary" @click="viewInvoiceAttachment(row)">查看</el-link>
<el-link type="primary" @click="downloadInvoiceAttachment(row)">下载</el-link>
<el-link v-if="editable" type="danger" @click="removeInvoice($index)">
删除
</el-link>
<el-link type="danger" @click="removeInvoice($index)">删除</el-link>
</div>
</template>
</el-table-column>
@@ -626,6 +630,39 @@
>
</el-dialog>
<el-dialog
v-model="detailAmountAdjust.visible"
title="结算明细调整"
width="520px"
append-to-body
>
<el-form
:model="detailAmountAdjust"
label-position="right"
label-width="auto"
class="formal-editor__amount-adjust"
>
<el-form-item label="原金额">
<span>{{ formatMoney(detailAmountAdjust.originalAmount) }}</span>
</el-form-item>
<el-form-item label="调整金额" required>
<el-input-number
v-model="detailAmountAdjust.adjustAmount"
:precision="2"
:step="0.01"
:controls="false"
/>
</el-form-item>
<el-form-item label="结算金额">
<span>{{ formatMoney(detailAmountAdjustSettlementAmount) }}</span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="detailAmountAdjust.visible = false">取消</el-button>
<el-button type="primary" @click="confirmDetailAmountAdjustment">提交</el-button>
</template>
</el-dialog>
<el-dialog v-model="adjust.visible" title="结算明细调整" width="92%" append-to-body>
<el-table v-loading="adjust.loading" :data="adjust.rows" border>
<el-table-column type="index" label="序号" width="64" align="center" />
@@ -671,14 +708,6 @@
:precision="2"
:controls="false" /></template
></el-table-column>
<el-table-column label="结算金额(不含税)" min-width="180" align="center"
><template #default="{ row }"
><el-input-number
v-model="row.settlementAmountNoTax"
:min="0"
:precision="2"
:controls="false" /></template
></el-table-column>
<el-table-column label="备注" min-width="180" align="center"
><template #default="{ row }"><el-input v-model="row.remark" maxlength="200" /></template
></el-table-column>
@@ -723,6 +752,7 @@ import {
summaryColumns,
} from '@/option/settlement/formalSettlementTable';
import { getDictionary } from '@/api/system/dictbiz';
import { h } from 'vue';
import { mapGetters } from 'vuex';
import { downloadFileByUrl } from '@/utils/util';
import * as XLSX from 'xlsx';
@@ -752,6 +782,7 @@ export default {
adjustments: [],
changeRecords: [],
attachments: [],
selectedAttachmentRows: [],
invoices: [],
invoiceClaimNo: '',
invoiceClaimLoading: false,
@@ -815,6 +846,12 @@ export default {
reason: '',
rows: [],
},
detailAmountAdjust: {
visible: false,
row: null,
originalAmount: 0,
adjustAmount: 0,
},
detailCollapsed: false,
detailQuery: {
documentNo: '',
@@ -865,6 +902,14 @@ export default {
summaryTotal() {
return this.summaryFees.reduce((total, row) => total + Number(row.settlementAmount || 0), 0);
},
detailAmountAdjustSettlementAmount() {
return Number(
(
Number(this.detailAmountAdjust.originalAmount || 0) +
Number(this.detailAmountAdjust.adjustAmount || 0)
).toFixed(2)
);
},
filteredDetails() {
return this.details.filter(row =>
Object.entries(this.appliedDetailQuery).every(([field, keyword]) => {
@@ -901,7 +946,14 @@ export default {
},
methods: {
async initialize() {
const newRecordAudit = this.recordId
? null
: {
createUserName: this.userInfo?.realName || this.userInfo?.userName || '',
createTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
};
this.form = createFormalSettlementForm();
if (newRecordAudit) Object.assign(this.form, newRecordAudit);
this.sources = [];
this.details = [];
this.summaryFees = [];
@@ -909,9 +961,16 @@ export default {
this.adjustments = [];
this.changeRecords = [];
this.attachments = [];
this.selectedAttachmentRows = [];
this.invoices = [];
this.invoiceClaimNo = '';
this.attachmentUploadFiles = [];
this.detailAmountAdjust = {
visible: false,
row: null,
originalAmount: 0,
adjustAmount: 0,
};
this.detailCollapsed = false;
this.resetDetailQuery(false);
await Promise.all([
@@ -923,6 +982,7 @@ export default {
if (!this.recordId) {
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
if (this.initialData) await this.applyInitialData();
Object.assign(this.form, newRecordAudit);
await this.refreshFormalSettlementNo();
return;
}
@@ -945,6 +1005,7 @@ export default {
this.adjustments = data.adjustments || [];
this.changeRecords = data.changeRecords || [];
this.attachments = this.parseAttachments(data.attachmentsJson);
this.selectedAttachmentRows = [];
this.invoices = data.invoices || [];
this.sortAttachments();
this.contracts = this.allContracts.filter(
@@ -1272,7 +1333,8 @@ export default {
this.buildSummaryFeesFromDetails();
this.detailCandidate.visible = false;
},
removeDetail(row) {
async removeDetail(row) {
await this.$confirm('确认将该明细踢出正式结算单?', '提示', { type: 'warning' });
const index = this.details.indexOf(row);
if (index < 0) return;
this.details.splice(index, 1);
@@ -1309,16 +1371,26 @@ export default {
isSummaryMoney(prop) {
return ['originalAmount', 'adjustAmount', 'settlementAmount'].includes(prop);
},
isNegativeAdjustedAmount(row, prop) {
return (
Number(row.adjustAmount || 0) < 0 && ['adjustAmount', 'settlementAmount'].includes(prop)
);
},
recalculateSummaryRow(row) {
row.settlementAmount = Number(row.originalAmount || 0) + Number(row.adjustAmount || 0);
},
getSummarySums({ columns, data }) {
const sumProps = ['originalAmount', 'adjustAmount', 'settlementAmount'];
const hasNegativeAdjustment = data.some(row => Number(row.adjustAmount || 0) < 0);
return columns.map((column, index) => {
if (index === 0) return '合计';
if (!sumProps.includes(column.property)) return '';
const total = data.reduce((sum, row) => sum + Number(row[column.property] || 0), 0);
return this.formatMoney(total);
const totalText = this.formatMoney(total);
if (column.property === 'settlementAmount' && hasNegativeAdjustment) {
return h('span', { style: { color: 'var(--el-color-danger)' } }, totalText);
}
return totalText;
});
},
buildSummaryFeesFromDetails() {
@@ -1406,6 +1478,20 @@ export default {
);
},
async openAdjustDialog(row) {
if (!row.formalSettlementId) {
const originalAmount = Number(
row.originalAmount ??
row.totalAmount ??
Number(row.settlementAmountTax || 0) - Number(row.adjustAmount || 0)
);
this.detailAmountAdjust = {
visible: true,
row,
originalAmount: Number(originalAmount.toFixed(2)),
adjustAmount: Number(row.adjustAmount || 0),
};
return;
}
this.adjust = {
visible: true,
loading: true,
@@ -1425,6 +1511,20 @@ export default {
this.adjust.loading = false;
}
},
confirmDetailAmountAdjustment() {
const settlementAmount = this.detailAmountAdjustSettlementAmount;
if (!Number.isFinite(settlementAmount) || settlementAmount < 0) {
return this.$message.warning('调整后的结算金额不能小于0');
}
const row = this.detailAmountAdjust.row;
if (!row) return;
row.originalAmount = this.detailAmountAdjust.originalAmount;
row.adjustAmount = Number(this.detailAmountAdjust.adjustAmount || 0);
row.settlementAmountTax = settlementAmount;
row.pendingDetailAdjustment = true;
this.buildSummaryFeesFromDetails();
this.detailAmountAdjust.visible = false;
},
async saveAdjustment() {
if (!this.adjust.reason.trim()) return this.$message.warning('请输入调整原因');
this.adjust.saving = true;
@@ -1586,6 +1686,17 @@ export default {
settlementType: this.form.settlementType,
sourcePreSettlementIds: this.form.sourcePreSettlementIds,
sourceDetailIds: this.form.sourceDetailIds,
detailAdjustments: this.details
.filter(row => row.pendingDetailAdjustment)
.map(row => ({
sourcePreSettlementDetailId:
row.sourcePreSettlementDetailId ||
(row.sourcePreSettlementId ? row.id : undefined),
sourceDetailId: row.sourcePreSettlementId
? undefined
: row.sourceDetailId || row.id,
adjustAmount: Number(row.adjustAmount || 0),
})),
exchangeRateDate: this.form.exchangeRateDate,
exchangeRate: this.form.exchangeRate,
summaryFees: this.summaryFees
@@ -1639,6 +1750,12 @@ export default {
const quantity = Number(value);
return Number.isFinite(quantity) ? quantity.toFixed(2) : '-';
},
formatDetailTransportQuantity(value) {
return this.readonly && Number(value) === -1 ? '-' : this.formatQuantity(value);
},
formatDetailMileage(value) {
return this.readonly && Number(value) === -1 ? '-' : this.displayValue(value);
},
async loadTransportTypeOptions() {
const { data } = await getDictionary({ code: 'transport_type' });
this.transportTypeOptions = data?.data || [];
@@ -1736,12 +1853,46 @@ export default {
});
this.sortAttachments();
},
handleAttachmentSelectionChange(rows) {
this.selectedAttachmentRows = rows || [];
},
removeAttachment(index) {
this.attachments.splice(index, 1);
this.selectedAttachmentRows = this.selectedAttachmentRows.filter(file =>
this.attachments.includes(file)
);
},
getAttachmentUrl(file = {}) {
return file.url || file.link || file.src || file.domain || '';
},
getAttachmentName(file = {}) {
return (
file.originalName || file.name || this.getAttachmentFileName(this.getAttachmentUrl(file))
);
},
downloadAttachment(file) {
const url = this.getAttachmentUrl(file);
if (!url) {
this.$message.warning('附件地址为空,无法下载');
return;
}
downloadFileByUrl(url, this.getAttachmentName(file) || '附件');
},
downloadAttachments() {
const files = this.selectedAttachmentRows.length
? this.selectedAttachmentRows
: this.attachments;
const downloadableFiles = files.filter(file => this.getAttachmentUrl(file));
if (!downloadableFiles.length) {
this.$message.warning(
this.selectedAttachmentRows.length ? '所选附件无法下载' : '暂无可下载附件'
);
return;
}
downloadableFiles.forEach((file, index) => {
window.setTimeout(() => this.downloadAttachment(file), index * 200);
});
},
previewAttachment(file) {
const url = this.getAttachmentUrl(file);
if (!url) return this.$message.warning('附件地址为空,无法预览');
@@ -1844,7 +1995,7 @@ export default {
}
.formal-editor__detail-filter-actions {
grid-column: 1 / -1;
justify-self: start;
justify-self: end;
}
.formal-editor__page-actions {
display: flex;
@@ -1892,12 +2043,20 @@ export default {
.formal-editor__adjust-reason {
margin-top: 16px;
}
.formal-editor__amount-adjust :deep(.el-input-number) {
width: 100%;
}
.formal-editor__attachment-missing {
margin-left: 12px;
color: var(--el-color-danger);
font-size: 13px;
font-weight: 400;
}
.formal-editor__attachment-actions {
display: flex;
justify-content: flex-end;
margin-bottom: 12px;
}
.formal-editor__attachment-upload {
margin-top: 12px;
}
@@ -77,9 +77,10 @@
>{{ displayValue(row[column.prop]) }}</el-tag
>
<span v-else-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
<span v-else-if="column.prop === 'invoiceStatusName'">{{
invoiceName(row.invoiceStatus)
}}</span>
<span
v-else-if="column.prop === 'invoiceStatusName'"
:class="['fs-table-panel__invoice-status', `is-${row.invoiceStatus || 'unreceived'}`]"
>{{ invoiceName(row.invoiceStatus, row.settlementType) }}</span>
<span v-else-if="column.prop === 'paymentStatusName'">{{
paymentName(row.paymentStatus)
}}</span>
@@ -120,7 +121,7 @@
<script>
import { Refresh } from '@element-plus/icons-vue';
import {
invoiceStatusOptions,
invoiceStatusName,
kingdeeSyncStatusOptions,
paymentStatusOptions,
} from '@/option/settlement/formalSettlementSearch';
@@ -210,8 +211,8 @@ export default {
''
);
},
invoiceName(value) {
return invoiceStatusOptions.find(item => item.value === value)?.label || value || '-';
invoiceName(value, settlementType) {
return invoiceStatusName(value, settlementType);
},
paymentName(value) {
return paymentStatusOptions.find(item => item.value === value)?.label || value || '-';
@@ -247,6 +248,17 @@ export default {
justify-content: center;
gap: 8px;
}
.fs-table-panel__invoice-status {
&.is-unreceived {
color: #f56c6c;
}
&.is-partial {
color: #e6a23c;
}
&.is-completed {
color: #67c23a;
}
}
.fs-table-panel__pagination {
display: flex;
justify-content: flex-end;
@@ -51,7 +51,7 @@
v-else-if="field.type === 'number'"
v-model="form[field.prop]"
:min="0"
:precision="6"
:precision="2"
:controls="false"
:disabled="!editable || form.currency === 'RMB'"
@change="recalculateLocalAmount"
@@ -104,10 +104,10 @@
@change="handleManualFeeTypeChange(row)"
>
<el-option
v-for="item in feeOptions"
:key="item.feeType"
:label="item.feeTypeName || item.feeType"
:value="item.feeType"
v-for="item in feeCategoryOptions"
:key="item.id || item.dictValue"
:label="item.dictKey || item.dictValue"
:value="item.dictValue"
/>
</el-select>
<el-select
@@ -899,14 +899,25 @@ export default {
currency: first.currency || 'RMB',
localCurrency: first.localCurrency || 'RMB',
});
this.details = rows.map(row => ({
this.details = rows.map(row => this.normalizeSourceDetail(row));
this.buildSummaryFeesFromDetails();
},
normalizeSourceDetail(row) {
const originalAmount =
row.originalAmount ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount ?? 0;
return {
...row,
sourceDetailId: row.sourceDetailId || row.id,
originalAmount,
adjustAmount: row.adjustAmount ?? 0,
settlementAmountTax:
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
row.settlementAmountTax ??
row.totalAmount ??
row.afterAmount ??
row.settlementAmount ??
originalAmount,
feeItemsJson: row.feeItemsJson || JSON.stringify(row.feeItems || {}),
}));
this.buildSummaryFeesFromDetails();
};
},
resetEditor() {
this.form = emptyPreSettlementForm();
@@ -1054,7 +1065,7 @@ export default {
sourceDetailIds: this.details.map(row => row.sourceDetailId || row.id),
summaryFees: this.summaryFees.map(row => ({
id: row.id || undefined,
feeType: row.feeType || '',
feeType: this.feeCategoryValue(row.feeType),
feeItem: row.feeItem,
originalAmount: Number(row.originalAmount || 0),
adjustAmount: Number(row.adjustAmount || 0),
@@ -1079,14 +1090,25 @@ export default {
if (!this.manualFeeItems(row.feeType).includes(row.feeItem)) row.feeItem = '';
},
manualFeeItems(feeType) {
return this.feeOptions.find(item => item.feeType === feeType)?.feeItems || [];
return (
this.feeOptions.find(
item => this.feeCategoryValue(item.feeType) === this.feeCategoryValue(feeType)
)?.feeItems || []
);
},
feeCategoryValue(value) {
if (value === undefined || value === null || value === '') return '';
const option = this.feeCategoryOptions.find(
item => String(item.dictKey) === String(value) || String(item.dictValue) === String(value)
);
return option?.dictValue || value;
},
feeCategoryName(value) {
if (value === undefined || value === null || value === '') return '';
const option = this.feeCategoryOptions.find(
item => String(item.dictKey) === String(value) || String(item.dictValue) === String(value)
);
return option?.dictValue || value;
return option?.dictKey || value;
},
transportTypeName(value) {
if (value === undefined || value === null || value === '') return '';
@@ -1124,7 +1146,7 @@ export default {
const feeOption = this.feeOptions.find(item =>
(item.feeItems || []).some(name => String(name) === String(feeItem))
);
const feeType = feeOption?.feeType || fallbackFeeType || '';
const feeType = this.feeCategoryValue(feeOption?.feeType || fallbackFeeType);
const key = `${feeType}\u0000${feeItem}`;
const current = summaryMap.get(key) || {
id: '',
@@ -1239,7 +1261,7 @@ export default {
const existingIds = new Set(this.details.map(row => String(row.sourceDetailId || row.id)));
this.candidateSelection.forEach(row => {
if (!existingIds.has(String(row.id))) {
this.details.push({ ...row, sourceDetailId: row.id });
this.details.push(this.normalizeSourceDetail(row));
}
});
this.candidateDialog.confirming = true;
@@ -42,6 +42,8 @@
? settlementTypeName
: field.prop === 'formalSettlementId'
? form.formalSettlementNo || form.formalSettlementId
: field.prop === 'createTime'
? formatCreateDate(form[field.prop])
: form[field.prop]
)
}}</span></el-form-item
@@ -68,23 +70,55 @@
v-if="editable && form.formalSettlementId"
type="primary"
plain
@click="openFeeDialog"
@click="addFeeRow"
>添加费用</el-button
>
</template>
<el-table :data="details" border>
<el-table-column type="index" label="序号" width="64" align="center" /><el-table-column
prop="feeType"
label="费用类型"
min-width="150"
align="center"
/><el-table-column
prop="feeItem"
label="费用项目"
min-width="180"
align="center"
show-overflow-tooltip
/><el-table-column
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column prop="feeType" label="费用类型" min-width="180" align="center">
<template #default="{ row }">
<el-select
v-if="editable && row.manualFlag === 1"
v-model="row.feeType"
filterable
allow-create
default-first-option
clearable
placeholder="请选择或输入"
>
<el-option
v-for="item in feeOptions"
:key="item.feeType"
:label="item.feeTypeName || item.feeType"
:value="item.feeType"
/>
</el-select>
<span v-else>{{ displayValue(feeTypeName(row.feeType)) }}</span>
</template>
</el-table-column>
<el-table-column prop="feeItem" label="费用项目" min-width="200" align="center">
<template #default="{ row }">
<el-select
v-if="editable && row.manualFlag === 1"
v-model="row.feeItem"
filterable
allow-create
default-first-option
clearable
placeholder="请选择或输入"
>
<el-option
v-for="item in feeItemOptions(row.feeType)"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<span v-else>{{ displayValue(row.feeItem) }}</span>
</template>
</el-table-column>
<el-table-column
prop="originalAmountTax"
label="原金额(含税)"
min-width="145"
@@ -98,7 +132,9 @@
><el-input-number
v-if="editable"
v-model="row.adjustmentAmountTax"
:class="{ negative: Number(row.adjustmentAmountTax) < 0 }"
:precision="2"
:step="0.01"
:controls="false"
@change="recalculate"
/><span v-else :class="{ negative: Number(row.adjustmentAmountTax) < 0 }">{{
@@ -134,7 +170,7 @@
></template
></el-table-column
>
<template #empty><el-empty description="请选择调整费用明细" /></template>
<template #empty><el-empty description="暂无调整费用" /></template>
</el-table>
<div class="settlement-adjustment-editor__summary">
调整金额合计<strong :class="{ negative: Number(form.adjustmentAmount) < 0 }">{{
@@ -210,53 +246,19 @@
</div>
<template #footer
><el-button @click="visible = false">取消</el-button
><el-button
v-if="editable"
type="primary"
plain
:loading="syncing"
:disabled="!form.formalSettlementId"
@click="handleSync"
>同步</el-button
><el-button v-if="editable" type="primary" :loading="saving" @click="handleSave"
>保存</el-button
></template
>
<el-dialog v-model="feeDialog.visible" title="选择调整费用" width="88%" append-to-body>
<el-table
ref="feeTable"
:data="feeRows"
border
@selection-change="feeDialog.selected = $event"
><el-table-column type="selection" width="52" align="center" /><el-table-column
type="index"
label="序号"
width="64"
align="center"
/><el-table-column
prop="documentNo"
label="单据号"
min-width="150"
align="center"
/><el-table-column
prop="feeType"
label="费用类型"
min-width="140"
align="center"
/><el-table-column
prop="feeItem"
label="费用项目"
min-width="180"
align="center"
show-overflow-tooltip
/><el-table-column
prop="originalAmountTax"
label="原金额(含税)"
min-width="145"
align="center"
><template #default="{ row }">{{
formatMoney(row.originalAmountTax)
}}</template></el-table-column
></el-table
>
<template #footer
><el-button @click="feeDialog.visible = false">取消</el-button
><el-button type="primary" @click="confirmFees">确定</el-button></template
>
</el-dialog>
</el-dialog>
</template>
@@ -274,6 +276,7 @@ export default {
data: () => ({
loading: false,
saving: false,
syncing: false,
form: createSettlementAdjustmentForm(),
fields: settlementAdjustmentFormFields,
details: [],
@@ -296,11 +299,10 @@ export default {
'zip',
],
candidates: [],
feeRows: [],
feeOptions: [],
rules: {
formalSettlementId: [{ required: true, message: '请选择关联正式结算单', trigger: 'change' }],
},
feeDialog: { visible: false, selected: [] },
}),
computed: {
visible: {
@@ -333,22 +335,29 @@ export default {
methods: {
async initialize() {
this.form = createSettlementAdjustmentForm();
const userInfo = this.$store.getters.userInfo || {};
this.form.createTime = this.$dayjs().format('YYYY-MM-DD');
this.form.createUserName = userInfo.realName || userInfo.userName || userInfo.account || '';
this.details = [];
this.attachments = [];
this.selectedAttachments = [];
this.candidates = [];
this.feeRows = [];
this.feeOptions = [];
if (!this.recordId) {
await this.loadFormalSettlements('');
await Promise.all([this.loadFormalSettlements(''), this.loadFeeOptions()]);
return;
}
this.loading = true;
try {
const response = await api.getDetail(this.recordId);
const [response] = await Promise.all([api.getDetail(this.recordId), this.loadFeeOptions()]);
const data = response?.data?.data || response?.data || response || {};
this.form = { ...createSettlementAdjustmentForm(), ...data };
this.details = (data.details || []).map(item => ({
...item,
manualFlag:
item.formalSettlementDetailId == null && item.formalSettlementDetailFeeId == null
? 1
: 0,
adjustmentAmountTax: Number(item.adjustmentAmountTax || 0),
adjustmentAmountNoTax:
item.adjustmentAmountNoTax == null ? null : Number(item.adjustmentAmountNoTax),
@@ -363,6 +372,10 @@ export default {
const { data } = await api.getFormalSettlements(keyword);
this.candidates = data?.data || data || [];
},
async loadFeeOptions() {
const { data } = await api.getFeeOptions();
this.feeOptions = data?.data || data || [];
},
async handleFormalChange(id) {
if (!id) {
Object.assign(this.form, {
@@ -397,6 +410,7 @@ export default {
if (String(this.form.formalSettlementId) !== String(formalSettlementId)) return;
this.details = (data?.data || data || []).map(fee => ({
...fee,
manualFlag: 0,
originalAmountTax: Number(fee.originalAmountTax || 0),
adjustmentAmountTax: 0,
adjustmentAmountNoTax: null,
@@ -407,25 +421,28 @@ export default {
this.loading = false;
}
},
async openFeeDialog() {
const { data } = await api.getFormalDetails(this.form.formalSettlementId);
const used = new Set(this.details.map(item => String(item.formalSettlementDetailFeeId)));
this.feeRows = (data?.data || data || []).filter(
item => !used.has(String(item.formalSettlementDetailFeeId))
);
this.feeDialog = { visible: true, selected: [] };
addFeeRow() {
this.details.push({
formalSettlementDetailId: null,
formalSettlementDetailFeeId: null,
feeType: '',
feeItem: '',
originalAmountTax: 0,
adjustmentAmountTax: 0,
adjustmentAmountNoTax: null,
remark: '',
manualFlag: 1,
});
},
confirmFees() {
this.feeDialog.selected.forEach(item =>
this.details.push({
...item,
adjustmentAmountTax: 0,
adjustmentAmountNoTax: null,
remark: '',
})
feeTypeName(value) {
return (
this.feeOptions.find(item => String(item.feeType) === String(value))?.feeTypeName || value
);
},
feeItemOptions(feeType) {
return (
this.feeOptions.find(item => String(item.feeType) === String(feeType))?.feeItems || []
);
this.recalculate();
this.feeDialog.visible = false;
},
recalculate() {
const total = this.details.reduce(
@@ -437,13 +454,65 @@ export default {
(Number(this.form.originalSettlementAmount || 0) + total).toFixed(2)
);
},
async handleSync() {
const formalSettlementId = this.form.formalSettlementId;
if (!formalSettlementId) return this.$message.warning('请先选择正式结算单');
this.syncing = true;
try {
const [candidateResponse, detailResponse] = await Promise.all([
api.getFormalSettlements(this.form.formalSettlementNo || ''),
api.getFormalDetails(formalSettlementId),
]);
if (String(this.form.formalSettlementId) !== String(formalSettlementId)) return;
const candidateData = candidateResponse?.data?.data || candidateResponse?.data || [];
const formalSettlements = Array.isArray(candidateData) ? candidateData : [];
const formalSettlement = formalSettlements.find(
item => String(item.id) === String(formalSettlementId)
);
if (formalSettlement) {
const { id, ...formalInformation } = formalSettlement;
Object.assign(this.form, formalInformation, {
formalSettlementId: id,
formalSettlementNo: formalSettlement.formalSettlementNo,
originalSettlementAmount: Number(formalSettlement.settlementAmount || 0),
settlementTypeName: formalSettlement.settlementTypeName,
});
}
const existingDetailMap = new Map(
this.details
.filter(item => item.formalSettlementDetailFeeId != null)
.map(item => [String(item.formalSettlementDetailFeeId), item])
);
const manualDetails = this.details.filter(item => Number(item.manualFlag) === 1);
const detailData = detailResponse?.data?.data || detailResponse?.data || [];
const formalDetails = Array.isArray(detailData) ? detailData : [];
this.details = formalDetails
.map(fee => {
const existing = existingDetailMap.get(String(fee.formalSettlementDetailFeeId));
return {
...fee,
manualFlag: 0,
originalAmountTax: Number(fee.originalAmountTax || 0),
adjustmentAmountTax: Number(existing?.adjustmentAmountTax || 0),
adjustmentAmountNoTax:
existing?.adjustmentAmountNoTax == null
? null
: Number(existing.adjustmentAmountNoTax),
remark: existing?.remark || '',
};
})
.concat(manualDetails);
this.recalculate();
this.$message.success('同步成功');
} finally {
this.syncing = false;
}
},
async handleSave() {
await this.$refs.formRef.validate();
if (!this.details.length) return this.$message.warning('请至少添加一条调整费用');
this.saving = true;
try {
await api.save({
...(this.recordId ? { id: this.form.id } : {}),
const { data } = await api.save({
id: this.form.id || undefined,
formalSettlementId: this.form.formalSettlementId,
attachmentsJson: JSON.stringify(this.attachments || []),
remark: this.form.remark,
@@ -452,11 +521,13 @@ export default {
formalSettlementDetailFeeId: item.formalSettlementDetailFeeId,
feeType: item.feeType,
feeItem: item.feeItem,
originalAmountTax: item.originalAmountTax,
adjustmentAmountTax: item.adjustmentAmountTax,
adjustmentAmountNoTax: item.adjustmentAmountNoTax,
remark: item.remark,
})),
});
this.form.id = data?.data || data || this.form.id;
this.$message.success('保存成功');
this.visible = false;
this.$emit('success');
@@ -470,6 +541,11 @@ export default {
formatMoney(value) {
return Number(value || 0).toFixed(2);
},
formatCreateDate(value) {
if (!value) return '';
const date = this.$dayjs(value);
return date.isValid() ? date.format('YYYY-MM-DD') : value;
},
handleAttachmentChange(files) {
const userInfo = this.$store.getters.userInfo || {};
const uploadUserName = userInfo.realName || userInfo.userName || '';
@@ -573,4 +649,7 @@ export default {
.negative {
color: #f56c6c;
}
.settlement-adjustment-editor :deep(.el-input-number.negative .el-input__inner) {
color: #f56c6c;
}
</style>