1、调整配载、总单

2、新增收付款模块
This commit is contained in:
2026-08-22 21:11:48 +08:00
parent 1962bee882
commit 5fcb82c0c0
51 changed files with 11663 additions and 1506 deletions
@@ -79,10 +79,18 @@
<el-table-column label="操作" width="120" fixed="right" align="center">
<template #default="{ row }">
<div class="settlement-detail-page__actions">
<el-link v-if="row.settlementStatus === 'pending'" type="primary" @click="openAdjustDialog(row)">
<el-link
v-if="row.settlementStatus === 'pending'"
type="primary"
@click="openAdjustDialog(row)"
>
调整
</el-link>
<el-link v-if="row.settlementStatus === 'pending'" type="danger" @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>
@@ -112,12 +120,7 @@
<header class="settlement-detail-page__detail-panel-header">
<h3>单据详情{{ detailDialog.title }}</h3>
<el-tooltip content="关闭" placement="top">
<el-button
:icon="Close"
text
aria-label="关闭单据详情"
@click="closeDetailPanel"
/>
<el-button :icon="Close" text aria-label="关闭单据详情" @click="closeDetailPanel" />
</el-tooltip>
</header>
<div class="settlement-detail-page__detail-panel-body">
@@ -180,12 +183,7 @@
<header class="settlement-detail-page__detail-panel-header">
<h3>调整费用{{ adjustDialog.row?.documentNo || '-' }}</h3>
<el-tooltip content="关闭" placement="top">
<el-button
:icon="Close"
text
aria-label="关闭调整费用"
@click="closeAdjustPanel"
/>
<el-button :icon="Close" text aria-label="关闭调整费用" @click="closeAdjustPanel" />
</el-tooltip>
</header>
<div
@@ -346,7 +344,6 @@
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadTransferCandidates">查询</el-button>
<el-button type="primary" @click="confirmTransferSelection">确认选择</el-button>
</el-form-item>
</el-form>
</div>
@@ -477,11 +474,7 @@
</div>
<template #footer>
<el-button @click="generateDialog.visible = false">取消</el-button>
<el-button
type="primary"
:loading="previewDialog.loading"
@click="openGeneratePreview"
>
<el-button type="primary" :loading="previewDialog.loading" @click="openGeneratePreview">
生成费用
</el-button>
</template>
@@ -513,7 +506,7 @@
</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" @click="backToGenerateDialog">上一步</el-button>
<el-button type="primary" :loading="previewDialog.submitting" @click="submitGenerateFee">
提交
</el-button>
@@ -537,9 +530,11 @@ import {
} from '@/option/settlement/receivable-payable-detail';
import * as api from '@/api/settlement/receivable-payable-detail';
import { getList as getContractList } from '@/api/business/contract-manage';
import { getContractOptions as getSettlementContractOptions } from '@/api/settlement/preSettlement';
import { exportBlob } from '@/api/common';
import { getDictionary } from '@/api/system/dictbiz';
import { downloadXls } from '@/utils/util';
import { createSettlementTransfer } from '@/utils/settlement-transfer';
export default {
props: {
@@ -567,7 +562,13 @@ export default {
rows: [],
selection: [],
page: { current: 1, size: 10, total: 0 },
detailDialog: { visible: false, title: '费用明细', activeTab: 'fee', row: null, loading: false },
detailDialog: {
visible: false,
title: '费用明细',
activeTab: 'fee',
row: null,
loading: false,
},
feeRows: [],
dynamicFeeColumns: [],
adjustDialog: { visible: false, loading: false, submitting: false, row: null },
@@ -664,7 +665,10 @@ export default {
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
if (column.prop === 'transportQuantity') return this.fixedTwoDecimals(row[column.prop]);
if (column.dynamic) {
return this.money(this.normalizeFeeItems(row.feeItems)[column.feeItemName], row.currency || 'RMB');
return this.money(
this.normalizeFeeItems(row.feeItems)[column.feeItemName],
row.currency || 'RMB'
);
}
return this.formatDetailCell(row, column.prop);
},
@@ -673,7 +677,12 @@ export default {
this.tableFeeItemNames = [];
try {
const params = this.buildRequestParams(
this.normalizeQuery(this.query, 'generateDateRange', 'generateStartDate', 'generateEndDate')
this.normalizeQuery(
this.query,
'generateDateRange',
'generateStartDate',
'generateEndDate'
)
);
const res = await api.getList(this.page.current, this.page.size, params);
const data = this.unwrapPage(res);
@@ -685,8 +694,7 @@ export default {
}
},
async loadContracts() {
const contractCategory =
this.settlementType === 'payable' ? '承运商合同' : '客户合同';
const contractCategory = this.settlementType === 'payable' ? '承运商合同' : '客户合同';
this.contractLoading = true;
try {
const res = await getContractList(1, 999, {
@@ -746,7 +754,13 @@ export default {
},
async openDetailDialog(row) {
this.closeAdjustPanel();
this.detailDialog = { ...this.detailDialog, visible: true, row, title: row.documentNo, activeTab: 'fee' };
this.detailDialog = {
...this.detailDialog,
visible: true,
row,
title: row.documentNo,
activeTab: 'fee',
};
await this.loadFeeDetail();
},
async openAdjustDialog(row) {
@@ -778,13 +792,9 @@ export default {
originalAmount: Number(item.originalAmount || 0),
feeItems,
manualFee: item.billingFactor === '手工调整',
feeItemName:
item.billingFactor === '手工调整' ? Object.keys(feeItems)[0] || '' : '',
feeItemName: item.billingFactor === '手工调整' ? Object.keys(feeItems)[0] || '' : '',
feeType: item.billingType === '手工扣费' ? 'deduct' : 'charge',
amount:
item.billingFactor === '手工调整'
? Math.abs(Number(item.afterAmount || 0))
: 0,
amount: item.billingFactor === '手工调整' ? Math.abs(Number(item.afterAmount || 0)) : 0,
};
if (adjusted.manualFee) this.recalculateManualAdjustRow(adjusted);
else this.recalculateAdjustRow(adjusted);
@@ -870,7 +880,9 @@ export default {
0
);
const hasFreightItem = Object.keys(row.feeItems).some(this.isFreightFeeItem);
row.afterAmount = Number((hasFreightItem ? feeItemTotal : Number(row.freightAmount || 0) + feeItemTotal).toFixed(2));
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) {
@@ -882,7 +894,8 @@ export default {
return;
}
const invalidManualRow = this.adjustRows.find(
row => row.manualFee && (!String(row.feeItemName || '').trim() || Number(row.amount || 0) <= 0)
row =>
row.manualFee && (!String(row.feeItemName || '').trim() || Number(row.amount || 0) <= 0)
);
if (invalidManualRow) {
this.$message.warning('请完整填写手工费用项目和金额');
@@ -1050,7 +1063,9 @@ export default {
return value !== null && value !== undefined && String(value).trim() !== '';
};
const normalizeSettlementType = value => {
const normalized = String(value || '').trim().toLowerCase();
const normalized = String(value || '')
.trim()
.toLowerCase();
if (normalized === '应收') return 'receivable';
if (normalized === '应付') return 'payable';
return normalized;
@@ -1061,12 +1076,13 @@ export default {
const expectedSettlementType = normalizeSettlementType(this.settlementType);
const matchesSettlementType =
!expectedSettlementType || rowSettlementType === expectedSettlementType;
const status = String(row.settlementStatus || '').trim().toLowerCase();
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);
) || ['closed', 'close', '已关闭'].includes(status);
const hasPreSettlement = [
row.preSettlementId,
row.preSettlementIds,
@@ -1087,13 +1103,6 @@ export default {
!hasFormalSettlement
);
},
confirmTransferSelection() {
if (!this.transferSelection.length) {
this.$message.warning('请选择需要转结算的明细');
return;
}
this.$message.success(`已选择${this.transferSelection.length}条明细`);
},
transferSelectionChange(selection) {
this.transferSelection = selection;
},
@@ -1102,20 +1111,57 @@ export default {
this.$message.warning('请选择需要转结算的明细');
return;
}
if (this.transferSelection.some(item => !item.contractId && !item.contractNo)) {
this.$message.warning('所选明细缺少合同信息,无法转结算');
return;
}
const contractKeys = new Set(
this.transferSelection.map(item =>
item.contractNo ? `no:${item.contractNo}` : `id:${item.contractId}`
)
);
if (contractKeys.size > 1) {
this.$message.warning('请选择同一合同下的明细进行转结算');
return;
}
this.transferDialog.submitting = true;
try {
await api.transferSettlement({
settlementType: this.settlementType || undefined,
settlementBillType: this.transferForm.settlementBillType,
ids: this.transferSelection.map(item => item.id),
const transferRows = await this.resolveTransferContractIds(this.transferSelection);
if (!transferRows) return;
const settlementBillType = this.transferForm.settlementBillType;
const transferToken = createSettlementTransfer({
settlementBillType,
settlementType: this.settlementType || transferRows[0]?.settlementType,
rows: transferRows,
});
this.$message.success('转结算成功');
const isPreSettlement = settlementBillType === 'pre';
this.transferDialog.visible = false;
this.loadTable();
await this.$router.push({
path: isPreSettlement
? '/settlement/pre-settlement/form'
: '/settlement/formal-settlement/form',
query: {
mode: 'add',
transferToken,
name: isPreSettlement ? '新增预结算' : '新增正式结算',
},
});
} finally {
this.transferDialog.submitting = false;
}
},
async resolveTransferContractIds(rows) {
if (rows.every(item => item.contractId)) return rows;
const contractNo = rows.find(item => item.contractNo)?.contractNo;
const { data } = await getSettlementContractOptions(contractNo);
const contracts = data?.data || [];
const contract = contracts.find(item => String(item.contractNo) === String(contractNo));
if (!contract?.id) {
this.$message.warning('未找到所选明细对应的有效合同,无法转结算');
return null;
}
return rows.map(item => ({ ...item, contractId: item.contractId || contract.id }));
},
openGenerateDialog() {
this.generateDialog.visible = true;
this.generateQuery = {};
@@ -1127,8 +1173,9 @@ export default {
this.loadContracts();
},
handleGenerateContractChange(contractId) {
this.syncBillingPlanOptions(contractId);
this.generateQuery.billingPlanId = '';
const options = this.syncBillingPlanOptions(contractId);
const selected = options.find(item => item.defaultPlan) || options[0];
this.generateQuery.billingPlanId = selected?.id || '';
},
async loadGenerateWaybills() {
if (!this.generateQuery.contractId || !this.generateQuery.billingPlanId) {
@@ -1138,9 +1185,18 @@ export default {
this.generateDialog.loading = true;
try {
const params = this.buildRequestParams(
this.normalizeQuery(this.generateQuery, 'finishDateRange', 'finishStartDate', 'finishEndDate')
this.normalizeQuery(
this.generateQuery,
'finishDateRange',
'finishStartDate',
'finishEndDate'
)
);
const res = await api.getGenerateWaybills(
this.generatePage.current,
this.generatePage.size,
params
);
const res = await api.getGenerateWaybills(this.generatePage.current, this.generatePage.size, params);
const data = this.unwrapPage(res);
this.generateRows = data.records || [];
this.generatePage.total = data.total || 0;
@@ -1188,12 +1244,49 @@ export default {
minWidth: 130,
align: 'right',
}));
const selectedWaybillsById = new Map();
const selectedWaybillsByNo = new Map();
this.generateSelection.forEach(item => {
[item.id, item.waybillId, item.sourceWaybillId].forEach(id => {
if (id !== null && id !== undefined && id !== '') {
selectedWaybillsById.set(String(id), item);
}
});
if (item.waybillNo) selectedWaybillsByNo.set(String(item.waybillNo), item);
});
this.previewRows = (data.records || []).map(row => {
const dynamic = {};
(data.feeItemNames || []).forEach((name, index) => {
dynamic[`feeItem${index}`] = row.feeItems?.[name] || '';
});
return { ...row, ...dynamic };
const sourceWaybillId =
row.waybillId || row.sourceWaybillId || row.sourceId || row.businessId || row.id;
const sourceWaybill =
selectedWaybillsById.get(String(sourceWaybillId || '')) ||
selectedWaybillsByNo.get(String(row.waybillNo || row.waybillNumber || ''));
return {
...row,
customerName:
row.customerName ||
row.customerUnitName ||
row.clientName ||
sourceWaybill?.customerName ||
'',
waybillNo:
row.waybillNo ||
row.waybillNumber ||
row.sourceWaybillNo ||
sourceWaybill?.waybillNo ||
'',
vehicleNo:
row.vehicleNo ||
row.plateNo ||
row.carNo ||
row.vehicleNumber ||
sourceWaybill?.vehicleNo ||
'',
...dynamic,
};
});
this.previewPage.total = data.total || 0;
return true;
@@ -1207,6 +1300,12 @@ export default {
this.previewPage.current = 1;
this.loadGeneratePreview();
},
backToGenerateDialog() {
this.previewDialog.visible = false;
this.$nextTick(() => {
this.generateDialog.visible = true;
});
},
async submitGenerateFee() {
this.previewDialog.submitting = true;
try {
@@ -1227,7 +1326,12 @@ export default {
exportBlob(
'/blade-transport/receivable-payable-detail/export-receivable-payable-detail',
this.buildRequestParams(
this.normalizeQuery(this.query, 'generateDateRange', 'generateStartDate', 'generateEndDate')
this.normalizeQuery(
this.query,
'generateDateRange',
'generateStartDate',
'generateEndDate'
)
),
{ feedback: true }
).then(res => {
@@ -1254,7 +1358,9 @@ export default {
`plan-${index}`,
name: item.name || item.planName || item.billingPlanName || `计费方案${index + 1}`,
defaultPlan:
item.defaultPlan === true || String(item.defaultPlan).toLowerCase() === 'true',
item.defaultPlan === true ||
item.defaultPlan === 1 ||
['true', '1'].includes(String(item.defaultPlan).toLowerCase()),
}));
return this.billingPlanOptions;
},