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
@@ -18,25 +18,22 @@
<el-row :gutter="24">
<el-col v-for="field in formFields" :key="field.prop" :span="field.span || 6">
<el-form-item :label="field.label" :prop="field.prop">
<el-select
<el-input
v-if="field.type === 'contract'"
v-model="form.contractId"
filterable
remote
clearable
:remote-method="loadContractOptions"
:loading="contractLoading"
v-model="form.contractName"
readonly
:disabled="!editable || details.length > 0"
placeholder="请选择合同"
@change="handleContractChange"
>
<el-option
v-for="item in contractOptions"
:key="item.id"
:label="`${item.contractName}${item.contractNo || '-'}`"
:value="item.id"
/>
</el-select>
<template #append>
<el-button
:disabled="!editable || details.length > 0"
@click="openContractDialog"
>
选择
</el-button>
</template>
</el-input>
<el-date-picker
v-else-if="field.type === 'date'"
v-model="form[field.prop]"
@@ -126,6 +123,9 @@
<el-input-number
v-else-if="editable && column.prop === 'adjustAmount'"
v-model="row.adjustAmount"
:class="{
'pre-settlement-editor__negative-amount': Number(row.adjustAmount || 0) < 0,
}"
:precision="2"
:controls="false"
@change="recalculateSummaryRow(row)"
@@ -136,7 +136,13 @@
maxlength="50"
show-word-limit
/>
<span v-else-if="isSummaryMoney(column.prop)">
<span
v-else-if="isSummaryMoney(column.prop)"
:class="{
'pre-settlement-editor__negative-amount':
column.prop === 'adjustAmount' && Number(row.adjustAmount || 0) < 0,
}"
>
{{ formatMoney(row[column.prop], form.currency) }}
</span>
<span v-else-if="column.prop === 'feeType'">
@@ -393,7 +399,9 @@
<template v-if="!pageMode" #footer>
<el-button @click="visible = false">取消</el-button>
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)">保存</el-button>
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)"
>保存</el-button
>
<el-button
v-if="editable && hasPermission('pre_settlement_submit')"
type="primary"
@@ -405,7 +413,9 @@
</template>
<div v-if="pageMode" class="pre-settlement-editor__page-actions">
<el-button @click="visible = false">取消</el-button>
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)">保存</el-button>
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)"
>保存</el-button
>
<el-button
v-if="editable && hasPermission('pre_settlement_submit')"
type="primary"
@@ -492,6 +502,164 @@
</template>
</el-dialog>
<el-dialog
v-model="contractDialog.visible"
title="选择合同"
width="92%"
append-to-body
destroy-on-close
class="pre-settlement-contract-dialog"
>
<el-form
:model="contractDialog.query"
inline
label-position="right"
label-width="auto"
class="pre-settlement-editor__contract-filter"
@submit.prevent
>
<el-form-item label="合同编号">
<el-input
v-model="contractDialog.query.contractNo"
clearable
placeholder="请输入"
@keyup.enter="loadContractRows"
/>
</el-form-item>
<el-form-item label="合同名称">
<el-input
v-model="contractDialog.query.contractName"
clearable
placeholder="请输入"
@keyup.enter="loadContractRows"
/>
</el-form-item>
<el-form-item label="所属项目">
<el-input
v-model="contractDialog.query.projectName"
clearable
placeholder="请输入"
@keyup.enter="loadContractRows"
/>
</el-form-item>
<el-form-item label="所属组织">
<el-input
v-model="contractDialog.query.organizationName"
clearable
placeholder="请输入"
@keyup.enter="loadContractRows"
/>
</el-form-item>
<el-form-item label="合同类别">
<el-select v-model="contractDialog.query.contractCategory" clearable 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="contractDialog.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="contractDialog.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="contractDialog.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="contractDialog.expanded" label="审核状态">
<el-select v-model="contractDialog.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="pre-settlement-editor__contract-filter-actions">
<el-button type="primary" @click="searchContractRows">查询</el-button>
<el-button @click="resetContractQuery">重置</el-button>
<el-button text @click="contractDialog.expanded = !contractDialog.expanded">
{{ contractDialog.expanded ? '收起' : '展开' }}
</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="contractDialog.loading"
:data="contractDialog.rows"
border
highlight-current-row
@current-change="contractDialog.current = $event"
@row-dblclick="selectContract"
>
<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="selectContract(row)">选择</el-link>
</template>
</el-table-column>
</el-table>
<div class="pre-settlement-editor__pagination">
<el-pagination
v-model:current-page="contractDialog.page.current"
v-model:page-size="contractDialog.page.size"
:total="contractDialog.page.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="loadContractRows"
@size-change="handleContractSizeChange"
/>
</div>
<template #footer>
<el-button @click="contractDialog.visible = false">取消</el-button>
<el-button type="primary" @click="selectContract(contractDialog.current)">确定</el-button>
</template>
</el-dialog>
<el-dialog
v-model="adjustDialog.visible"
title="结算明细调整"
@@ -613,7 +781,7 @@ import { mapGetters } from 'vuex';
import {
adjustDetail,
getCandidateDetails,
getContractOptions,
getContractList,
getDetail,
getDetailFees,
getFeeOptions,
@@ -627,6 +795,13 @@ import {
preSettlementFormFields,
preSettlementFormRules,
} from '@/option/settlement/preSettlementForm';
import {
contractApprovalStatusOptions,
contractCategoryOptions,
contractStageOptions,
effectiveTypeOptions,
signTypeOptions,
} from '@/option/business/common';
import {
advanceColumns,
candidateDetailColumns,
@@ -675,8 +850,45 @@ export default {
advanceTableColumns: advanceColumns,
changeTableColumns: changeRecordColumns,
candidateColumns: candidateDetailColumns,
contractLoading: false,
contractOptions: [],
contractCategoryOptions,
signTypeOptions,
effectiveTypeOptions,
contractStageOptions,
contractApprovalStatusOptions,
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 },
],
contractDialog: {
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 },
},
feeOptions: [],
feeCategoryOptions: [],
transportTypeOptions: [],
@@ -857,7 +1069,6 @@ export default {
},
async initialize() {
this.resetEditor();
await this.loadContractOptions('');
await this.loadFeeOptions();
await this.loadFeeCategoryOptions();
if (this.pageMode) await this.loadTransportTypeOptions();
@@ -935,6 +1146,12 @@ export default {
this.changeRecords = [];
this.attachments = [];
this.selectedAttachmentRows = [];
this.contractOptions = [];
this.contractDialog.visible = false;
this.contractDialog.expanded = false;
this.contractDialog.rows = [];
this.contractDialog.current = null;
this.contractDialog.page = { current: 1, size: 10, total: 0 };
this.$nextTick(() => this.$refs.formRef?.clearValidate());
},
async loadDetail() {
@@ -969,15 +1186,85 @@ export default {
this.loading = false;
}
},
async loadContractOptions(keyword = '') {
this.contractLoading = true;
openContractDialog() {
if (!this.editable || this.details.length > 0) return;
this.contractDialog.visible = true;
this.contractDialog.expanded = false;
this.contractDialog.current = null;
this.contractDialog.page.current = 1;
this.loadContractRows();
},
async loadContractRows() {
this.contractDialog.loading = true;
try {
const { data } = await getContractOptions(keyword);
this.contractOptions = data?.data || [];
const { data } = await getContractList(
this.contractDialog.page.current,
this.contractDialog.page.size,
{ ...this.contractDialog.query }
);
const page = data?.data || {};
this.contractDialog.rows = page.records || [];
this.contractDialog.page.total = Number(page.total || 0);
} finally {
this.contractLoading = false;
this.contractDialog.loading = false;
}
},
searchContractRows() {
this.contractDialog.page.current = 1;
this.loadContractRows();
},
resetContractQuery() {
this.contractDialog.query = {
contractNo: '',
contractName: '',
projectName: '',
organizationName: '',
contractCategory: '',
signType: '',
effectiveType: '',
contractStage: '',
approvalStatus: '',
};
this.searchContractRows();
},
handleContractSizeChange() {
this.contractDialog.page.current = 1;
this.loadContractRows();
},
selectContract(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,
settlementType:
row.settlementType || (row.contractCategory === '客户合同' ? 'receivable' : 'payable'),
};
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.handleContractChange(contract.id);
this.contractDialog.visible = false;
this.contractDialog.current = null;
this.$refs.formRef?.clearValidate('contractId');
},
contractCategoryName(value) {
return (
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
this.displayValue(value)
);
},
signTypeName(value) {
return (
this.signTypeOptions.find(item => String(item.value) === String(value))?.label ||
this.displayValue(value)
);
},
async loadFeeOptions() {
const { data } = await getFeeOptions();
this.feeOptions = data?.data || [];
@@ -993,6 +1280,7 @@ export default {
handleContractChange(id) {
const contract = this.contractOptions.find(item => String(item.id) === String(id));
if (!contract) {
this.form.contractId = '';
this.form.contractNo = '';
this.form.contractName = '';
this.form.projectId = '';
@@ -1004,6 +1292,7 @@ export default {
this.summaryFees = [];
return;
}
this.form.contractId = contract.id;
this.form.contractNo = contract.contractNo;
this.form.contractName = contract.contractName;
this.form.projectId = contract.projectId;
@@ -1166,9 +1455,7 @@ export default {
this.details.forEach(row => {
const feeItems = this.parseFeeItems(row.feeItemsJson || row.feeItems);
const feeItemEntries = Object.entries(feeItems);
feeItemEntries.forEach(([feeItem, amount]) =>
appendSummary(feeItem, amount, row.feeType)
);
feeItemEntries.forEach(([feeItem, amount]) => appendSummary(feeItem, amount, row.feeType));
let knownAmount = feeItemEntries.reduce(
(total, [, amount]) => total + Number(amount || 0),
@@ -1186,11 +1473,7 @@ export default {
}
const totalAmount = Number(
row.settlementAmountTax ??
row.totalAmount ??
row.afterAmount ??
row.settlementAmount ??
0
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount ?? 0
);
const residualAmount = Number((totalAmount - knownAmount).toFixed(2));
if (Math.abs(residualAmount) >= 0.005) {
@@ -1621,6 +1904,39 @@ export default {
margin-left: auto;
}
&__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%;
}
}
&__contract-filter-actions {
grid-column: 1 / -1;
width: 100%;
justify-content: flex-end;
:deep(.el-form-item__content) {
justify-content: flex-end;
}
}
&__attachment-actions {
display: flex;
align-items: center;
@@ -1649,6 +1965,14 @@ export default {
font-weight: 600;
}
&__negative-amount {
color: var(--el-color-danger);
}
:deep(.pre-settlement-editor__negative-amount .el-input__inner) {
color: var(--el-color-danger);
}
&__pagination {
display: flex;
justify-content: flex-end;
@@ -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;