1、新增结算模块
2、调整业务模块 3、调整客商模块
This commit is contained in:
@@ -21,7 +21,9 @@
|
||||
<div class="settlement-detail-page__search-actions">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
<el-button :icon="searchExpanded ? ArrowUp : ArrowDown" @click="toggleSearch" />
|
||||
<el-button text :icon="searchExpanded ? ArrowUp : ArrowDown" @click="toggleSearch">
|
||||
{{ searchExpanded ? '收起' : '展开' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -31,8 +33,8 @@
|
||||
<div class="settlement-detail-page__toolbar">
|
||||
<div class="settlement-detail-page__toolbar-left">
|
||||
<el-button type="primary" @click="openGenerateDialog">生成费用</el-button>
|
||||
<el-button type="primary" @click="openUpdateFeeDialog">更新费用</el-button>
|
||||
<el-button type="primary" @click="openTransferDialog">批量转结算</el-button>
|
||||
<el-button type="primary" plain @click="openUpdateFeeDialog">更新费用</el-button>
|
||||
<el-button type="primary" plain @click="openTransferDialog">批量转结算</el-button>
|
||||
<el-button type="primary" plain @click="handleExport">导出</el-button>
|
||||
</div>
|
||||
<div class="settlement-detail-page__toolbar-right">
|
||||
@@ -55,7 +57,7 @@
|
||||
<el-table-column type="selection" width="52" fixed="left" align="center" />
|
||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in tableColumns"
|
||||
v-for="column in displayTableColumns"
|
||||
:key="column.prop"
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
@@ -71,16 +73,16 @@
|
||||
>
|
||||
{{ row[column.prop] }}
|
||||
</el-link>
|
||||
<span v-else>{{ row[column.prop] || '-' }}</span>
|
||||
<span v-else>{{ formatColumnValue(row, column) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="settlement-detail-page__actions">
|
||||
<el-link v-if="row.settlementStatus === 'pending'" type="primary" @click="openUpdateFeeDialog(row)">
|
||||
<el-link v-if="row.settlementStatus === 'pending'" type="primary" @click="openAdjustDialog(row)">
|
||||
调整
|
||||
</el-link>
|
||||
<el-link v-if="row.settlementStatus === 'pending'" type="primary" @click="closeRow(row)">
|
||||
<el-link v-if="row.settlementStatus === 'pending'" type="danger" @click="closeRow(row)">
|
||||
关闭
|
||||
</el-link>
|
||||
<el-link v-else type="primary" disabled>-</el-link>
|
||||
@@ -122,7 +124,7 @@
|
||||
:align="column.align || 'center'"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ formatCell(row[column.prop]) }}</template>
|
||||
<template #default="{ row }">{{ formatDetailCell(row, column.prop) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
@@ -154,6 +156,134 @@
|
||||
</el-tabs>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="adjustDialog.visible"
|
||||
title="调整费用"
|
||||
width="96%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<div class="settlement-detail-page__adjust-toolbar">
|
||||
<el-button type="primary" plain @click="addManualAdjustRow">新增费用项</el-button>
|
||||
</div>
|
||||
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="手工费用项目" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="row.manualFee"
|
||||
v-model="row.feeItemName"
|
||||
clearable
|
||||
placeholder="请输入收费或扣费项目"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收费/扣费" width="130" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-if="row.manualFee"
|
||||
v-model="row.feeType"
|
||||
placeholder="请选择"
|
||||
@change="recalculateManualAdjustRow(row)"
|
||||
>
|
||||
<el-option label="收费" value="charge" />
|
||||
<el-option label="扣费" value="deduct" />
|
||||
</el-select>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手工金额" width="150" align="right">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-if="row.manualFee"
|
||||
v-model="row.amount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
@change="recalculateManualAdjustRow(row)"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link
|
||||
v-if="row.manualFee && !row.id"
|
||||
type="danger"
|
||||
@click="removeManualAdjustRow(row)"
|
||||
>
|
||||
删除
|
||||
</el-link>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="column in adjustFeeColumns"
|
||||
:key="column.prop || column.feeItemName"
|
||||
:label="column.label"
|
||||
:min-width="column.minWidth"
|
||||
:align="column.align || 'center'"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-if="column.prop === 'transportQuantityText' && !row.manualFee"
|
||||
v-model="row.transportQuantity"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
@change="recalculateAdjustRow(row)"
|
||||
/>
|
||||
<el-input-number
|
||||
v-else-if="column.prop === 'mileage' && !row.manualFee"
|
||||
v-model="row.mileage"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
@change="recalculateAdjustRow(row)"
|
||||
/>
|
||||
<el-input-number
|
||||
v-else-if="column.prop === 'freightAmount' && !row.manualFee"
|
||||
v-model="row.freightAmount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
@change="recalculateAdjustRow(row, 'freight')"
|
||||
/>
|
||||
<el-input-number
|
||||
v-else-if="column.dynamic && !row.manualFee"
|
||||
v-model="row.feeItems[column.feeItemName]"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
@change="recalculateAdjustRow(row, column.feeItemName)"
|
||||
/>
|
||||
<el-input
|
||||
v-else-if="column.prop === 'remark'"
|
||||
v-model="row.remark"
|
||||
clearable
|
||||
maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
<span v-else-if="column.prop === 'adjustAmountText'">
|
||||
{{ fixedTwoDecimals(row.adjustAmount) }}
|
||||
</span>
|
||||
<span v-else-if="column.prop === 'afterAmountText'">
|
||||
{{ fixedTwoDecimals(row.afterAmount) }}
|
||||
</span>
|
||||
<span v-else>{{ formatDetailCell(row, column.prop) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="adjustDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="adjustDialog.submitting" @click="saveAdjustFee">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="updateFeeDialog.visible" title="更新费用" width="620px" append-to-body>
|
||||
<el-form
|
||||
ref="updateFeeFormRef"
|
||||
@@ -169,9 +299,10 @@
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择合同"
|
||||
@change="handleUpdateContractChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractOptions"
|
||||
v-for="item in updateContractOptions"
|
||||
:key="item.id"
|
||||
:label="item.contractName"
|
||||
:value="item.id"
|
||||
@@ -179,7 +310,13 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同计费方案" prop="billingPlanId">
|
||||
<el-select v-model="updateFeeForm.billingPlanId" clearable filterable placeholder="请选择">
|
||||
<el-select
|
||||
v-model="updateFeeForm.billingPlanId"
|
||||
clearable
|
||||
filterable
|
||||
:disabled="!updateFeeForm.contractId"
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in billingPlanOptions"
|
||||
:key="item.id"
|
||||
@@ -190,10 +327,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" :loading="updateFeeDialog.submitting" @click="confirmUpdateFee">
|
||||
确认
|
||||
</el-button>
|
||||
<el-button @click="updateFeeDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="updateFeeDialog.submitting" @click="confirmUpdateFee">
|
||||
提交
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -201,7 +338,10 @@
|
||||
<div class="settlement-detail-page__transfer-form">
|
||||
<el-form :model="transferQuery" inline label-position="right" label-width="120px">
|
||||
<el-form-item label="转结算类型">
|
||||
<el-radio-group v-model="transferForm.settlementBillType">
|
||||
<el-radio-group
|
||||
v-model="transferForm.settlementBillType"
|
||||
@change="loadTransferCandidates"
|
||||
>
|
||||
<el-radio label="pre">预结算单</el-radio>
|
||||
<el-radio label="formal">正式结算单</el-radio>
|
||||
</el-radio-group>
|
||||
@@ -246,13 +386,15 @@
|
||||
:min-width="column.minWidth"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
>
|
||||
<template #default="{ row }">{{ formatColumnValue(row, column) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button type="primary" :loading="transferDialog.submitting" @click="submitTransfer">
|
||||
确认
|
||||
</el-button>
|
||||
<el-button @click="transferDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="transferDialog.submitting" @click="submitTransfer">
|
||||
提交
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -323,7 +465,7 @@
|
||||
<el-link v-if="column.link && row[column.prop]" type="primary">
|
||||
{{ row[column.prop] }}
|
||||
</el-link>
|
||||
<span v-else>{{ row[column.prop] || '-' }}</span>
|
||||
<span v-else>{{ formatColumnValue(row, column) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -339,8 +481,8 @@
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="openGeneratePreview">生成费用</el-button>
|
||||
<el-button @click="generateDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="openGeneratePreview">生成费用</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -369,11 +511,11 @@
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="previewDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="generateDialog.visible = true">上一步</el-button>
|
||||
<el-button type="primary" :loading="previewDialog.submitting" @click="submitGenerateFee">
|
||||
确认
|
||||
提交
|
||||
</el-button>
|
||||
<el-button @click="previewDialog.visible = false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
@@ -395,6 +537,7 @@ import {
|
||||
import * as api from '@/api/settlement/receivable-payable-detail';
|
||||
import { getList as getContractList } from '@/api/business/contract-manage';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
|
||||
export default {
|
||||
@@ -412,6 +555,7 @@ export default {
|
||||
Setting,
|
||||
searchFields,
|
||||
tableColumns,
|
||||
tableFeeItemNames: [],
|
||||
changeRecordColumns,
|
||||
transferSearchFields,
|
||||
generateWaybillColumns,
|
||||
@@ -424,6 +568,9 @@ export default {
|
||||
detailDialog: { visible: false, title: '费用明细', activeTab: 'fee', row: null, loading: false },
|
||||
feeRows: [],
|
||||
dynamicFeeColumns: [],
|
||||
adjustDialog: { visible: false, loading: false, submitting: false, row: null },
|
||||
adjustRows: [],
|
||||
adjustDynamicFeeColumns: [],
|
||||
changeRows: [],
|
||||
changePage: { current: 1, size: 10, total: 0 },
|
||||
changeDialog: { loading: false },
|
||||
@@ -433,6 +580,8 @@ export default {
|
||||
contractId: [{ required: true, message: '请选择更新范围', trigger: 'change' }],
|
||||
},
|
||||
contractOptions: [],
|
||||
updateContractOptions: [],
|
||||
transportTypeOptions: [],
|
||||
billingPlanOptions: [],
|
||||
transferDialog: { visible: false, loading: false, submitting: false },
|
||||
transferQuery: {},
|
||||
@@ -456,22 +605,67 @@ export default {
|
||||
return '应收应付';
|
||||
},
|
||||
visibleSearchFields() {
|
||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 7);
|
||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
||||
},
|
||||
feeDetailColumns() {
|
||||
return [...feeDetailBaseColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns];
|
||||
},
|
||||
displayTableColumns() {
|
||||
const columns = [...this.tableColumns];
|
||||
const totalIndex = columns.findIndex(column => column.prop === 'totalAmountText');
|
||||
const dynamicColumns = this.tableFeeItemNames.map((name, index) => ({
|
||||
label: name,
|
||||
prop: `tableFeeItem${index}`,
|
||||
feeItemName: name,
|
||||
dynamic: true,
|
||||
minWidth: 130,
|
||||
align: 'right',
|
||||
}));
|
||||
if (totalIndex < 0) return [...columns, ...dynamicColumns];
|
||||
columns.splice(totalIndex, 0, ...dynamicColumns);
|
||||
return columns;
|
||||
},
|
||||
previewColumns() {
|
||||
return [...generatePreviewColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns];
|
||||
},
|
||||
adjustFeeColumns() {
|
||||
return [
|
||||
...feeDetailBaseColumns,
|
||||
...this.adjustDynamicFeeColumns,
|
||||
...feeDetailTailColumns,
|
||||
];
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadTransportTypeOptions();
|
||||
this.loadContracts();
|
||||
this.loadTable();
|
||||
},
|
||||
methods: {
|
||||
async loadTransportTypeOptions() {
|
||||
const res = await getDictionary({ code: 'transport_type' });
|
||||
const records = res.data?.data || [];
|
||||
this.transportTypeOptions = records.map(item => ({
|
||||
label: item.dictValue || item.label || item.name,
|
||||
value: item.dictKey || item.value || item.dictValue || item.name,
|
||||
}));
|
||||
},
|
||||
transportTypeLabel(value) {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
return (
|
||||
this.transportTypeOptions.find(item => String(item.value) === String(value))?.label || value
|
||||
);
|
||||
},
|
||||
formatColumnValue(row, column) {
|
||||
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
|
||||
if (column.dynamic) {
|
||||
return this.money(this.normalizeFeeItems(row.feeItems)[column.feeItemName], row.currency || 'RMB');
|
||||
}
|
||||
return this.formatDetailCell(row, column.prop);
|
||||
},
|
||||
async loadTable() {
|
||||
this.loading = true;
|
||||
this.tableFeeItemNames = [];
|
||||
try {
|
||||
const params = this.buildRequestParams(
|
||||
this.normalizeQuery(this.query, 'generateDateRange', 'generateStartDate', 'generateEndDate')
|
||||
@@ -479,6 +673,7 @@ export default {
|
||||
const res = await api.getList(this.page.current, this.page.size, params);
|
||||
const data = this.unwrapPage(res);
|
||||
this.rows = (data.records || []).map(this.decorateRow);
|
||||
this.tableFeeItemNames = this.collectFeeItemNames(this.rows);
|
||||
this.page.total = data.total || 0;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
@@ -511,6 +706,132 @@ export default {
|
||||
this.detailDialog = { ...this.detailDialog, visible: true, row, title: row.documentNo, activeTab: 'fee' };
|
||||
await this.loadFeeDetail();
|
||||
},
|
||||
async openAdjustDialog(row) {
|
||||
this.adjustDialog = { ...this.adjustDialog, visible: true, row, loading: true };
|
||||
try {
|
||||
const res = await api.getFeeDetail(row.id);
|
||||
const data = res.data?.data || res.data || res || {};
|
||||
this.adjustDynamicFeeColumns = (data.feeItemNames || []).map(name => ({
|
||||
label: name,
|
||||
feeItemName: name,
|
||||
dynamic: true,
|
||||
minWidth: 150,
|
||||
align: 'right',
|
||||
}));
|
||||
this.adjustRows = (data.records || []).map(item => {
|
||||
const feeItems = {};
|
||||
(data.feeItemNames || []).forEach(name => {
|
||||
feeItems[name] = Number(item.feeItems?.[name] || 0);
|
||||
});
|
||||
const adjusted = {
|
||||
...item,
|
||||
transportQuantity: Number(item.transportQuantity || 0),
|
||||
mileage:
|
||||
item.mileage === null || item.mileage === undefined || Number(item.mileage) === -1
|
||||
? null
|
||||
: Number(item.mileage),
|
||||
freightAmount: Number(item.freightAmount || 0),
|
||||
originalAmount: Number(item.originalAmount || 0),
|
||||
feeItems,
|
||||
manualFee: item.billingFactor === '手工调整',
|
||||
feeItemName:
|
||||
item.billingFactor === '手工调整' ? Object.keys(feeItems)[0] || '' : '',
|
||||
feeType: item.billingType === '手工扣费' ? 'deduct' : 'charge',
|
||||
amount:
|
||||
item.billingFactor === '手工调整'
|
||||
? Math.abs(Number(item.afterAmount || 0))
|
||||
: 0,
|
||||
};
|
||||
if (adjusted.manualFee) this.recalculateManualAdjustRow(adjusted);
|
||||
else this.recalculateAdjustRow(adjusted);
|
||||
return adjusted;
|
||||
});
|
||||
} finally {
|
||||
this.adjustDialog.loading = false;
|
||||
}
|
||||
},
|
||||
addManualAdjustRow() {
|
||||
this.adjustRows.push({
|
||||
id: null,
|
||||
manualFee: true,
|
||||
feeItemName: '',
|
||||
feeType: 'charge',
|
||||
amount: 0,
|
||||
transportQuantity: null,
|
||||
mileage: null,
|
||||
freightAmount: 0,
|
||||
originalAmount: 0,
|
||||
feeItems: {},
|
||||
remark: '',
|
||||
});
|
||||
},
|
||||
removeManualAdjustRow(row) {
|
||||
const index = this.adjustRows.indexOf(row);
|
||||
if (index >= 0) this.adjustRows.splice(index, 1);
|
||||
},
|
||||
recalculateManualAdjustRow(row) {
|
||||
const amount = Number(row.amount || 0);
|
||||
row.afterAmount = Number((row.feeType === 'deduct' ? -amount : amount).toFixed(2));
|
||||
row.adjustAmount = Number((row.afterAmount - Number(row.originalAmount || 0)).toFixed(2));
|
||||
},
|
||||
fixedTwoDecimals(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
recalculateAdjustRow(row, changedField) {
|
||||
if (changedField && changedField !== 'freight' && this.isFreightFeeItem(changedField)) {
|
||||
row.freightAmount = Number(row.feeItems[changedField] || 0);
|
||||
}
|
||||
if (changedField === 'freight') {
|
||||
const freightItem = Object.keys(row.feeItems).find(this.isFreightFeeItem);
|
||||
if (freightItem) row.feeItems[freightItem] = Number(row.freightAmount || 0);
|
||||
}
|
||||
const feeItemTotal = Object.values(row.feeItems).reduce(
|
||||
(total, value) => total + Number(value || 0),
|
||||
0
|
||||
);
|
||||
const hasFreightItem = Object.keys(row.feeItems).some(this.isFreightFeeItem);
|
||||
row.afterAmount = Number((hasFreightItem ? feeItemTotal : Number(row.freightAmount || 0) + feeItemTotal).toFixed(2));
|
||||
row.adjustAmount = Number((row.afterAmount - Number(row.originalAmount || 0)).toFixed(2));
|
||||
},
|
||||
isFreightFeeItem(name) {
|
||||
return String(name || '').includes('运费') || String(name || '').includes('运输费');
|
||||
},
|
||||
async saveAdjustFee() {
|
||||
if (!this.adjustRows.length || !this.adjustDialog.row) {
|
||||
this.$message.warning('没有可调整的费用明细');
|
||||
return;
|
||||
}
|
||||
const invalidManualRow = this.adjustRows.find(
|
||||
row => row.manualFee && (!String(row.feeItemName || '').trim() || Number(row.amount || 0) <= 0)
|
||||
);
|
||||
if (invalidManualRow) {
|
||||
this.$message.warning('请完整填写手工费用项目和金额');
|
||||
return;
|
||||
}
|
||||
this.adjustDialog.submitting = true;
|
||||
try {
|
||||
await api.adjustFee({
|
||||
detailId: this.adjustDialog.row.id,
|
||||
rows: this.adjustRows.map(row => ({
|
||||
id: row.id,
|
||||
transportQuantity: row.transportQuantity,
|
||||
mileage: row.mileage,
|
||||
freightAmount: row.freightAmount,
|
||||
feeItems: row.feeItems,
|
||||
manualFee: row.manualFee === true,
|
||||
feeItemName: row.feeItemName,
|
||||
feeType: row.feeType,
|
||||
amount: row.amount,
|
||||
remark: row.remark,
|
||||
})),
|
||||
});
|
||||
this.$message.success('保存成功');
|
||||
this.adjustDialog.visible = false;
|
||||
await this.loadTable();
|
||||
} finally {
|
||||
this.adjustDialog.submitting = false;
|
||||
}
|
||||
},
|
||||
async loadFeeDetail() {
|
||||
if (!this.detailDialog.row) return;
|
||||
this.detailDialog.loading = true;
|
||||
@@ -555,20 +876,36 @@ export default {
|
||||
this.changePage.current = 1;
|
||||
this.loadChangeRecords();
|
||||
},
|
||||
openUpdateFeeDialog(row) {
|
||||
async openUpdateFeeDialog(row) {
|
||||
await this.loadUpdateFeeContracts();
|
||||
this.updateFeeDialog = { ...this.updateFeeDialog, visible: true, row: row || null };
|
||||
this.updateFeeForm = {
|
||||
ids: row ? [row.id] : this.selection.map(item => item.id),
|
||||
settlementType: this.settlementType || undefined,
|
||||
contractId: row?.contractId || '',
|
||||
billingPlanId: '',
|
||||
};
|
||||
this.syncBillingPlanOptions(this.updateFeeForm.contractId);
|
||||
this.handleUpdateContractChange(this.updateFeeForm.contractId);
|
||||
},
|
||||
async loadUpdateFeeContracts() {
|
||||
const res = await api.getUpdateFeeContracts({
|
||||
settlementType: this.settlementType || undefined,
|
||||
});
|
||||
this.updateContractOptions = res.data?.data || [];
|
||||
},
|
||||
handleUpdateContractChange(contractId) {
|
||||
this.updateFeeForm.billingPlanId = '';
|
||||
if (!contractId) {
|
||||
this.billingPlanOptions = [];
|
||||
return;
|
||||
}
|
||||
const options = this.syncBillingPlanOptions(contractId);
|
||||
const selected = options.find(item => item.defaultPlan) || options[0];
|
||||
this.updateFeeForm.billingPlanId = selected?.id || '';
|
||||
},
|
||||
async confirmUpdateFee() {
|
||||
await this.$refs.updateFeeFormRef.validate();
|
||||
await this.$confirm(
|
||||
'更新费用将使用最新的合同计费规则重新生成费用明细,确认更新费用?',
|
||||
'将使用选定计费方案,更新该合同下所有待结算明细的费用,确认继续?',
|
||||
'确认提示',
|
||||
{ type: 'warning' }
|
||||
);
|
||||
@@ -602,20 +939,73 @@ export default {
|
||||
},
|
||||
async loadTransferCandidates() {
|
||||
this.transferDialog.loading = true;
|
||||
this.transferSelection = [];
|
||||
try {
|
||||
const params = this.buildRequestParams(
|
||||
this.normalizeQuery(this.transferQuery, 'generateDateRange', 'generateStartDate', 'generateEndDate')
|
||||
this.normalizeQuery(
|
||||
this.transferQuery,
|
||||
'generateDateRange',
|
||||
'generateStartDate',
|
||||
'generateEndDate'
|
||||
)
|
||||
);
|
||||
const res = await api.getTransferCandidates(1, 50, {
|
||||
...params,
|
||||
settlementStatus: 'pending',
|
||||
settlementType: this.settlementType || undefined,
|
||||
settlementBillType: this.transferForm.settlementBillType,
|
||||
});
|
||||
const data = this.unwrapPage(res);
|
||||
this.transferRows = (data.records || []).map(this.decorateRow);
|
||||
this.transferRows = (data.records || [])
|
||||
.filter(row => this.isTransferCandidate(row))
|
||||
.map(this.decorateRow);
|
||||
} finally {
|
||||
this.transferDialog.loading = false;
|
||||
}
|
||||
},
|
||||
isTransferCandidate(row) {
|
||||
const hasValue = value => {
|
||||
if (Array.isArray(value)) return value.length > 0;
|
||||
return value !== null && value !== undefined && String(value).trim() !== '';
|
||||
};
|
||||
const normalizeSettlementType = value => {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
if (normalized === '应收') return 'receivable';
|
||||
if (normalized === '应付') return 'payable';
|
||||
return normalized;
|
||||
};
|
||||
const rowSettlementType = normalizeSettlementType(
|
||||
row.settlementType || row.settlementTypeName
|
||||
);
|
||||
const expectedSettlementType = normalizeSettlementType(this.settlementType);
|
||||
const matchesSettlementType =
|
||||
!expectedSettlementType || rowSettlementType === expectedSettlementType;
|
||||
const status = String(row.settlementStatus || '').trim().toLowerCase();
|
||||
const isClosed =
|
||||
[row.closed, row.isClosed, row.closeFlag, row.closedFlag].some(
|
||||
value => value === true || String(value).trim().toLowerCase() === 'true'
|
||||
) ||
|
||||
['closed', 'close', '已关闭'].includes(status);
|
||||
const hasPreSettlement = [
|
||||
row.preSettlementId,
|
||||
row.preSettlementIds,
|
||||
row.preSettlementNo,
|
||||
row.preSettlementNos,
|
||||
].some(hasValue);
|
||||
const hasFormalSettlement = [
|
||||
row.formalSettlementId,
|
||||
row.formalSettlementIds,
|
||||
row.formalSettlementNo,
|
||||
row.formalSettlementNos,
|
||||
].some(hasValue);
|
||||
return (
|
||||
matchesSettlementType &&
|
||||
['pending', '待结算'].includes(status) &&
|
||||
!isClosed &&
|
||||
!hasPreSettlement &&
|
||||
!hasFormalSettlement
|
||||
);
|
||||
},
|
||||
confirmTransferSelection() {
|
||||
if (!this.transferSelection.length) {
|
||||
this.$message.warning('请选择需要转结算的明细');
|
||||
@@ -763,14 +1153,16 @@ export default {
|
||||
return this.settlementType ? { ...params, settlementType: this.settlementType } : params;
|
||||
},
|
||||
syncBillingPlanOptions(contractId) {
|
||||
const contract = this.contractOptions.find(item => String(item.id) === String(contractId));
|
||||
const contracts = [...this.updateContractOptions, ...this.contractOptions];
|
||||
const contract = contracts.find(item => String(item.id) === String(contractId));
|
||||
const plans = this.parseJson(contract?.billingPlanJson);
|
||||
this.billingPlanOptions = plans.length
|
||||
? plans.map((item, index) => ({
|
||||
id: item.id || item.planId || item.name || `plan-${index}`,
|
||||
name: item.name || item.planName || item.billingPlanName || `计费方案${index + 1}`,
|
||||
}))
|
||||
: [{ id: 'default', name: '默认计费方案' }];
|
||||
this.billingPlanOptions = plans.map((item, index) => ({
|
||||
id: item.id || item.planId || item.name || `plan-${index}`,
|
||||
name: item.name || item.planName || item.billingPlanName || `计费方案${index + 1}`,
|
||||
defaultPlan:
|
||||
item.defaultPlan === true || String(item.defaultPlan).toLowerCase() === 'true',
|
||||
}));
|
||||
return this.billingPlanOptions;
|
||||
},
|
||||
normalizeQuery(source, rangeProp, startProp, endProp) {
|
||||
const params = { ...source };
|
||||
@@ -796,10 +1188,41 @@ export default {
|
||||
'-',
|
||||
};
|
||||
},
|
||||
collectFeeItemNames(rows) {
|
||||
const names = [];
|
||||
(rows || []).forEach(row => {
|
||||
Object.keys(this.normalizeFeeItems(row.feeItems)).forEach(name => {
|
||||
if (!names.includes(name)) names.push(name);
|
||||
});
|
||||
});
|
||||
return names;
|
||||
},
|
||||
normalizeFeeItems(value) {
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string' || !value.trim()) return {};
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
money(value, currency) {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
return `${Number(value).toFixed(2)} ${currency}`;
|
||||
},
|
||||
formatDetailCell(row, prop) {
|
||||
if (
|
||||
prop === 'mileage' &&
|
||||
(row?.[prop] === null ||
|
||||
row?.[prop] === undefined ||
|
||||
row?.[prop] === '' ||
|
||||
Number(row[prop]) === -1)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
return this.formatCell(row?.[prop]);
|
||||
},
|
||||
formatCell(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : value;
|
||||
},
|
||||
@@ -840,7 +1263,7 @@ export default {
|
||||
|
||||
.settlement-detail-page__search-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(220px, 1fr));
|
||||
grid-template-columns: repeat(4, minmax(220px, 1fr));
|
||||
gap: 8px 24px;
|
||||
align-items: start;
|
||||
|
||||
@@ -863,10 +1286,6 @@ export default {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.settlement-detail-page__table-panel {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.settlement-detail-page__toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -885,6 +1304,7 @@ export default {
|
||||
.settlement-detail-page__table,
|
||||
.settlement-detail-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
background: #fff;
|
||||
|
||||
:deep(th.el-table__cell) {
|
||||
background: #f5f7fa;
|
||||
@@ -924,13 +1344,13 @@ export default {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
@media screen and (max-width: 1200px) {
|
||||
.settlement-detail-page__search-grid {
|
||||
grid-template-columns: repeat(4, minmax(220px, 1fr));
|
||||
grid-template-columns: repeat(2, minmax(220px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
@media screen and (max-width: 760px) {
|
||||
.settlement-detail-page__search-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user