1、调整收付款

2、调整基础配置
3、调整结算
This commit is contained in:
2026-09-01 17:35:53 +08:00
parent f858eb2bcd
commit 80a15fc626
7 changed files with 932 additions and 121 deletions
@@ -150,7 +150,7 @@
</el-table>
</el-tab-pane>
<el-tab-pane label="变更记录" name="change">
<el-table v-loading="changeDialog.loading" :data="changeRows" border>
<el-table v-loading="latestChangeLoading" :data="latestChangeRows" border>
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column
v-for="column in changeRecordColumns"
@@ -161,17 +161,17 @@
align="center"
show-overflow-tooltip
/>
<el-table-column label="操作" width="120" fixed="right" align="center">
<template #default>
<el-link type="primary" @click="openChangeRecordsDialog">查看详情</el-link>
</template>
</el-table-column>
</el-table>
<div class="settlement-detail-page__pagination">
<el-pagination
v-model:current-page="changePage.current"
v-model:page-size="changePage.size"
:total="changePage.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="loadChangeRecords"
@size-change="handleChangeSizeChange"
/>
<div
v-if="!latestChangeLoading && !latestChangeRows.length"
class="settlement-detail-page__empty"
>
暂无变更记录
</div>
</el-tab-pane>
</el-tabs>
@@ -182,6 +182,37 @@
</section>
</teleport>
<el-dialog
v-model="changeRecordsDialog.visible"
title="变更记录详情"
width="88%"
append-to-body
>
<el-table v-loading="changeRecordsDialog.loading" :data="changeRows" border>
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column
v-for="column in changeRecordColumns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
:min-width="column.minWidth"
align="center"
show-overflow-tooltip
/>
</el-table>
<div class="settlement-detail-page__pagination">
<el-pagination
v-model:current-page="changePage.current"
v-model:page-size="changePage.size"
:total="changePage.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="loadChangeRecords"
@size-change="handleChangeSizeChange"
/>
</div>
</el-dialog>
<teleport to="body">
<section
v-if="adjustDialog.visible"
@@ -365,19 +396,12 @@
inputmode="decimal"
@input="value => handleAdjustFeeItemInput(row, column.feeItemName, value)"
/>
<el-input
v-else-if="column.prop === 'remark'"
v-model="row.remark"
clearable
maxlength="200"
placeholder="请输入备注"
/>
<el-input
v-else-if="column.prop === 'changeReason'"
v-model="row.changeReason"
clearable
maxlength="300"
placeholder="请输入变更原因"
placeholder="请输入调整原因"
/>
<span v-else-if="column.prop === 'adjustAmountText'">
{{ fixedTwoDecimals(row.adjustAmount) }}
@@ -545,22 +569,16 @@
label-width="120px"
>
<el-form-item label="运单合同" required>
<el-select
v-model="generateQuery.contractId"
<el-input
:model-value="generateContractName"
class="settlement-detail-page__generate-select"
filterable
clearable
:loading="contractLoading"
readonly
placeholder="请选择合同"
@change="handleGenerateContractChange"
>
<el-option
v-for="item in contractOptions"
:key="item.id"
:label="item.contractName"
:value="item.id"
/>
</el-select>
<template #append>
<el-button @click="openGenerateContractDialog">选择</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label="计费方案" required>
<el-select
@@ -673,6 +691,180 @@
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="generateContractDialog.visible"
title="选择合同"
width="92%"
append-to-body
destroy-on-close
class="settlement-detail-page__contract-dialog"
>
<el-form
:model="generateContractDialog.query"
label-position="right"
label-width="auto"
class="settlement-detail-page__contract-filter"
@submit.prevent
>
<el-form-item label="合同编号">
<el-input
v-model="generateContractDialog.query.contractNo"
clearable
placeholder="请输入"
@keyup.enter="loadGenerateContracts"
/>
</el-form-item>
<el-form-item label="合同名称">
<el-input
v-model="generateContractDialog.query.contractName"
clearable
placeholder="请输入"
@keyup.enter="loadGenerateContracts"
/>
</el-form-item>
<el-form-item label="所属项目">
<el-input
v-model="generateContractDialog.query.projectName"
clearable
placeholder="请输入"
@keyup.enter="loadGenerateContracts"
/>
</el-form-item>
<el-form-item label="所属组织">
<el-input
v-model="generateContractDialog.query.organizationName"
clearable
placeholder="请输入"
@keyup.enter="loadGenerateContracts"
/>
</el-form-item>
<el-form-item label="合同类别">
<el-select :model-value="generateContractCategory" disabled placeholder="请选择">
<el-option
v-for="item in contractCategoryOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="签约类型">
<el-select v-model="generateContractDialog.query.signType" clearable placeholder="请选择">
<el-option
v-for="item in signTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="生效类型">
<el-select
v-model="generateContractDialog.query.effectiveType"
clearable
placeholder="请选择"
>
<el-option
v-for="item in effectiveTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="合同阶段">
<el-select
v-model="generateContractDialog.query.contractStage"
clearable
placeholder="请选择"
>
<el-option
v-for="item in contractStageOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="generateContractDialog.expanded" label="审核状态">
<el-select
v-model="generateContractDialog.query.approvalStatus"
clearable
placeholder="请选择"
>
<el-option
v-for="item in contractApprovalStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item class="settlement-detail-page__contract-filter-actions">
<el-button type="primary" @click="searchGenerateContracts">查询</el-button>
<el-button @click="resetGenerateContractQuery">重置</el-button>
<el-button
text
@click="generateContractDialog.expanded = !generateContractDialog.expanded"
>
{{ generateContractDialog.expanded ? '收起' : '展开' }}
</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="generateContractDialog.loading"
:data="generateContractDialog.rows"
border
highlight-current-row
@current-change="generateContractDialog.current = $event"
@row-dblclick="selectGenerateContract"
>
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column
v-for="column in contractSelectionColumns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
:min-width="column.minWidth"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">
<span v-if="column.prop === 'organizationName'">{{
row.organizationName || row.deptName || '-'
}}</span>
<span v-else-if="column.prop === 'contractCategory'">{{
contractCategoryName(row.contractCategory)
}}</span>
<span v-else-if="column.prop === 'signType'">{{ signTypeName(row.signType) }}</span>
<span v-else>{{ displayValue(row[column.prop]) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="90" fixed="right" align="center">
<template #default="{ row }">
<el-link type="primary" @click.stop="selectGenerateContract(row)">选择</el-link>
</template>
</el-table-column>
</el-table>
<div class="settlement-detail-page__pagination">
<el-pagination
v-model:current-page="generateContractDialog.page.current"
v-model:page-size="generateContractDialog.page.size"
:total="generateContractDialog.page.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="loadGenerateContracts"
@size-change="handleGenerateContractSizeChange"
/>
</div>
<template #footer>
<el-button @click="generateContractDialog.visible = false">取消</el-button>
<el-button type="primary" @click="selectGenerateContract(generateContractDialog.current)">
确定
</el-button>
</template>
</el-dialog>
</basic-container>
</template>
@@ -698,6 +890,28 @@ import { exportBlob } from '@/api/common';
import { getDictionary } from '@/api/system/dictbiz';
import { downloadXls } from '@/utils/util';
import { createSettlementTransfer } from '@/utils/settlement-transfer';
import {
contractApprovalStatusOptions,
contractCategoryOptions,
contractStageOptions,
effectiveTypeOptions,
signTypeOptions,
} from '@/option/business/common';
const contractSelectionColumns = [
{ prop: 'contractNo', label: '合同编号', minWidth: 180 },
{ prop: 'contractName', label: '合同名称', minWidth: 220 },
{ prop: 'projectName', label: '所属项目', minWidth: 170 },
{ prop: 'organizationName', label: '所属组织', minWidth: 170 },
{ prop: 'contractCategory', label: '合同类别', minWidth: 130 },
{ prop: 'signType', label: '签约类型', minWidth: 120 },
{ prop: 'partyA', label: '甲方', minWidth: 170 },
{ prop: 'partyB', label: '乙方', minWidth: 170 },
{ prop: 'startDate', label: '开始日期', minWidth: 130 },
{ prop: 'endDate', label: '结束日期', minWidth: 130 },
{ prop: 'temporaryStartDate', label: '临时效力起', minWidth: 130 },
{ prop: 'temporaryEndDate', label: '临时效力止', minWidth: 130 },
];
const ADJUST_BILLING_ELEMENTS = [
'按重量',
@@ -777,15 +991,22 @@ export default {
priceUnitRequest: null,
changeRows: [],
changePage: { current: 1, size: 10, total: 0 },
changeDialog: { loading: false },
latestChangeRows: [],
latestChangeLoading: false,
changeRecordsDialog: { visible: false, loading: false },
updateFeeDialog: { visible: false, submitting: false, row: null },
updateFeeForm: { contractId: '', billingPlanId: '' },
updateFeeRules: {
contractId: [{ required: true, message: '请选择更新范围', trigger: 'change' }],
},
contractOptions: [],
contractLoading: false,
updateContractOptions: [],
contractCategoryOptions,
signTypeOptions,
effectiveTypeOptions,
contractStageOptions,
contractApprovalStatusOptions,
contractSelectionColumns,
transportTypeOptions: [],
billingPlanOptions: [],
transferDialog: { visible: false, loading: false, submitting: false },
@@ -796,6 +1017,25 @@ export default {
transferPage: { current: 1, size: 10, total: 0 },
generateDialog: { visible: false, loading: false },
generateQuery: {},
generateContractDialog: {
visible: false,
loading: false,
expanded: false,
rows: [],
current: null,
query: {
contractNo: '',
contractName: '',
projectName: '',
organizationName: '',
contractCategory: '',
signType: '',
effectiveType: '',
contractStage: '',
approvalStatus: '',
},
page: { current: 1, size: 10, total: 0 },
},
generateRows: [],
generateSelection: [],
generatePage: { current: 1, size: 10, total: 0 },
@@ -805,6 +1045,30 @@ export default {
};
},
computed: {
generateContractName() {
const contract = this.contractOptions.find(
item => String(item.id) === String(this.generateQuery.contractId)
);
return contract?.contractName || '';
},
generateContractCategory() {
return this.settlementType === 'payable' ? '承运商合同' : '客户合同';
},
contractCategoryName(value) {
return (
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
this.formatCell(value)
);
},
signTypeName(value) {
return (
this.signTypeOptions.find(item => String(item.value) === String(value))?.label ||
this.formatCell(value)
);
},
displayValue(value) {
return this.formatCell(value);
},
settlementTypeLabel() {
if (this.settlementType === 'receivable') return '应收';
if (this.settlementType === 'payable') return '应付';
@@ -830,14 +1094,31 @@ export default {
column => this.settlementType !== 'receivable' || column.prop !== 'preSettlementNo'
);
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',
}));
const dynamicColumns = this.isPayable
? [
{
label: '运输费',
prop: 'tableFreightAmount',
feeSummaryType: 'freight',
minWidth: 130,
align: 'right',
},
{
label: '其他费用',
prop: 'tableOtherFeeAmount',
feeSummaryType: 'other',
minWidth: 130,
align: 'right',
},
]
: 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;
@@ -846,12 +1127,14 @@ export default {
return [...generatePreviewColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns];
},
adjustFeeColumns() {
return [
...feeDetailBaseColumns,
...this.adjustDynamicFeeColumns,
...feeDetailTailColumns,
{ label: '变更原因', prop: 'changeReason', minWidth: 220 },
];
const tailColumns = feeDetailTailColumns.filter(column => column.prop !== 'remark');
const afterAmountIndex = tailColumns.findIndex(column => column.prop === 'afterAmountText');
tailColumns.splice(afterAmountIndex + 1, 0, {
label: '调整原因',
prop: 'changeReason',
minWidth: 220,
});
return [...feeDetailBaseColumns, ...this.adjustDynamicFeeColumns, ...tailColumns];
},
transportCargoTypeOptions() {
return this.cargoTypeOptions.filter(item => item.children?.length);
@@ -1114,6 +1397,12 @@ export default {
formatColumnValue(row, column) {
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
if (column.prop === 'transportQuantity') return this.fixedTwoDecimals(row[column.prop]);
if (column.feeSummaryType) {
return this.money(
this.summarizeTableFee(row, column.feeSummaryType),
row.currency || 'RMB'
);
}
if (column.dynamic) {
return this.money(
this.normalizeFeeItems(row.feeItems)[column.feeItemName],
@@ -1137,25 +1426,80 @@ 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.tableFeeItemNames = this.isPayable ? [] : this.collectFeeItemNames(this.rows);
this.page.total = data.total || 0;
} finally {
this.loading = false;
}
},
async loadContracts() {
const contractCategory = this.settlementType === 'payable' ? '承运商合同' : '客户合同';
this.contractLoading = true;
openGenerateContractDialog() {
this.generateContractDialog.visible = true;
this.generateContractDialog.expanded = false;
this.generateContractDialog.current = null;
this.generateContractDialog.page.current = 1;
this.loadGenerateContracts();
},
async loadGenerateContracts() {
this.generateContractDialog.loading = true;
try {
const res = await getContractList(1, 999, {
contractCategory,
});
const res = await getContractList(
this.generateContractDialog.page.current,
this.generateContractDialog.page.size,
{
...this.generateContractDialog.query,
contractCategory: this.generateContractCategory,
approvalStatuses: 'approved,change_approved',
}
);
const data = this.unwrapPage(res);
this.contractOptions = data.records || [];
this.generateContractDialog.rows = data.records || [];
this.generateContractDialog.page.total = Number(data.total || 0);
} finally {
this.contractLoading = false;
this.generateContractDialog.loading = false;
}
},
searchGenerateContracts() {
this.generateContractDialog.page.current = 1;
this.loadGenerateContracts();
},
resetGenerateContractQuery() {
this.generateContractDialog.query = {
contractNo: '',
contractName: '',
projectName: '',
organizationName: '',
contractCategory: '',
signType: '',
effectiveType: '',
contractStage: '',
approvalStatus: '',
};
this.searchGenerateContracts();
},
handleGenerateContractSizeChange() {
this.generateContractDialog.page.current = 1;
this.loadGenerateContracts();
},
selectGenerateContract(row) {
if (!row) {
this.$message.warning('请选择合同');
return;
}
const contract = {
...row,
deptId: row.deptId || row.organizationId,
deptName: row.deptName || row.organizationName,
partyA: row.partyA || row.payerName,
partyB: row.partyB || row.payeeName,
};
const index = this.contractOptions.findIndex(item => String(item.id) === String(contract.id));
if (index >= 0) this.contractOptions.splice(index, 1, contract);
else this.contractOptions.push(contract);
this.generateQuery.contractId = contract.id;
this.handleGenerateContractChange(contract.id);
this.generateContractDialog.visible = false;
this.generateContractDialog.current = null;
},
handleSearch() {
this.page.current = 1;
this.loadTable();
@@ -1177,6 +1521,7 @@ export default {
closeDetailPanel() {
this.detailDialog.visible = false;
this.detailDialog.row = null;
this.changeRecordsDialog.visible = false;
},
closeAdjustPanel() {
this.clearAdjustCalculations();
@@ -1525,11 +1870,31 @@ export default {
}
},
handleDetailTabChange(name) {
if (name === 'change') this.loadChangeRecords();
if (name === 'change') this.loadLatestChangeRecord();
},
async loadLatestChangeRecord() {
if (!this.detailDialog.row) return;
this.latestChangeLoading = true;
this.latestChangeRows = [];
try {
const res = await api.getChangeRecords(1, 1, {
detailId: this.detailDialog.row.id,
});
const data = this.unwrapPage(res);
this.latestChangeRows = (data.records || []).slice(0, 1);
} finally {
this.latestChangeLoading = false;
}
},
openChangeRecordsDialog() {
if (!this.detailDialog.row) return;
this.changeRecordsDialog.visible = true;
this.changePage.current = 1;
this.loadChangeRecords();
},
async loadChangeRecords() {
if (!this.detailDialog.row) return;
this.changeDialog.loading = true;
this.changeRecordsDialog.loading = true;
try {
const res = await api.getChangeRecords(this.changePage.current, this.changePage.size, {
detailId: this.detailDialog.row.id,
@@ -1538,7 +1903,7 @@ export default {
this.changeRows = data.records || [];
this.changePage.total = data.total || 0;
} finally {
this.changeDialog.loading = false;
this.changeRecordsDialog.loading = false;
}
},
handleChangeSizeChange() {
@@ -1792,11 +2157,26 @@ export default {
this.generateDialog.visible = true;
this.generateQuery = {};
this.contractOptions = [];
this.generateContractDialog.query = {
contractNo: '',
contractName: '',
projectName: '',
organizationName: '',
contractCategory: '',
signType: '',
effectiveType: '',
contractStage: '',
approvalStatus: '',
};
this.generateContractDialog.visible = false;
this.generateContractDialog.expanded = false;
this.generateContractDialog.rows = [];
this.generateContractDialog.current = null;
this.generateContractDialog.page = { current: 1, size: 10, total: 0 };
this.billingPlanOptions = [];
this.generateRows = [];
this.generateSelection = [];
this.generatePage = { current: 1, size: 10, total: 0 };
this.loadContracts();
},
handleGenerateContractChange(contractId) {
const options = this.syncBillingPlanOptions(contractId);
@@ -1911,6 +2291,7 @@ export default {
row.vehicleNumber ||
sourceWaybill?.vehicleNo ||
'',
transportQuantityText: row.transportQuantityText ?? row.transportQuantity ?? '',
...dynamic,
};
});
@@ -2032,6 +2413,16 @@ export default {
return {};
}
},
summarizeTableFee(row, summaryType) {
const feeItemEntries = Object.entries(this.normalizeFeeItems(row.feeItems));
const matchedEntries = feeItemEntries.filter(([name]) =>
summaryType === 'freight' ? this.isFreightFeeItem(name) : !this.isFreightFeeItem(name)
);
if (matchedEntries.length) {
return matchedEntries.reduce((total, [, amount]) => total + Number(amount || 0), 0);
}
return summaryType === 'freight' ? row.freightAmount : row.otherFeeAmount;
},
money(value, currency) {
if (value === null || value === undefined || value === '') return '-';
return `${Number(value).toFixed(2)} ${currency}`;
@@ -2160,6 +2551,12 @@ export default {
padding: 16px 0;
}
.settlement-detail-page__empty {
padding: 24px 0;
color: #909399;
text-align: center;
}
.settlement-detail-page__dialog-form {
padding: 16px 20px;
background: #fff;
@@ -2212,6 +2609,39 @@ export default {
width: 220px;
}
.settlement-detail-page__contract-filter {
display: grid !important;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px 24px;
margin-bottom: 16px;
:deep(.el-form-item) {
display: flex;
min-width: 0;
margin-bottom: 0;
}
:deep(.el-form-item__content) {
flex: 1;
min-width: 0;
}
:deep(.el-input),
:deep(.el-select) {
width: 100%;
}
}
.settlement-detail-page__contract-filter-actions {
grid-column: 1 / -1;
width: 100%;
justify-content: flex-end;
:deep(.el-form-item__content) {
justify-content: flex-end;
}
}
.settlement-detail-page__transfer-form {
margin-bottom: 16px;
padding: 12px 12px 4px;