1、调整业务模块

This commit is contained in:
2026-09-02 03:22:51 +08:00
parent 80a15fc626
commit 252df72f87
19 changed files with 1354 additions and 177 deletions
@@ -400,7 +400,7 @@
<div class="formal-editor__links">
<el-link type="primary" @click="viewInvoiceAttachment(row)">查看</el-link>
<el-link type="primary" @click="downloadInvoiceAttachment(row)">下载</el-link>
<el-link type="danger" @click="removeInvoice($index)">删除</el-link>
<el-link v-if="editable" type="danger" @click="removeInvoice($index)">删除</el-link>
</div>
</template>
</el-table-column>
@@ -456,6 +456,9 @@
<el-table-column prop="paymentTypeName" label="付款类型" min-width="120" align="center">
<template #default="{ row }">{{ displayValue(row.paymentTypeName) }}</template>
</el-table-column>
<el-table-column label="付款方式" min-width="140" align="center">
<template #default="{ row }">{{ paymentMethodName(row.paymentMethod) }}</template>
</el-table-column>
<el-table-column prop="paymentNo" label="单据号" min-width="160" align="center" />
<el-table-column prop="appliedAmount" label="申请付款金额" min-width="145" align="center">
<template #default="{ row }">{{ formatMoney(row.appliedAmount) }}</template>
@@ -1784,6 +1787,14 @@ export default {
displayValue(value) {
return value === null || value === undefined || value === '' ? '-' : value;
},
paymentMethodName(value) {
const labels = {
bank_transfer: '银行转账',
bank_draft: '银行承兑汇票',
commercial_draft: '商业承兑汇票',
};
return this.displayValue(labels[value] || value);
},
unwrapData(response) {
const body = response?.data || response || {};
return body?.data || body;
@@ -121,7 +121,9 @@
/>
</el-select>
<el-input-number
v-else-if="editable && column.prop === 'adjustAmount'"
v-else-if="
editable && column.prop === 'adjustAmount' && Number(row.manualFlag) === 1
"
v-model="row.adjustAmount"
:class="{
'pre-settlement-editor__negative-amount': Number(row.adjustAmount || 0) < 0,
@@ -671,6 +673,20 @@
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table-column prop="cargoName" label="货物名称" min-width="140" align="center" />
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
<el-table-column label="计费规则" min-width="190" align="center">
<template #default="{ row }">
<div class="pre-settlement-editor__billing-rule-cell">
<span>{{ displayValue(billingRuleName(row)) }}</span>
<el-icon
class="pre-settlement-editor__billing-rule-icon"
title="查看计费规则详情"
@click="openBillingRuleInfo(row)"
>
<InfoFilled />
</el-icon>
</div>
</template>
</el-table-column>
<el-table-column label="运输总量" min-width="150" align="center">
<template #default="{ row }">
<span v-if="adjustDialog.readonly">{{ formatQuantity(row) }}</span>
@@ -705,18 +721,6 @@
/>
</template>
</el-table-column>
<el-table-column label="运费" min-width="140" align="center">
<template #default="{ row }">
<el-input-number
v-model="row.freightAmount"
:min="0"
:precision="2"
:controls="false"
:disabled="adjustDialog.readonly"
@change="recalculateAdjustRow(row, 'freight')"
/>
</template>
</el-table-column>
<el-table-column
v-for="feeItem in adjustFeeItemNames"
:key="feeItem"
@@ -774,10 +778,76 @@
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="billingRuleDialog.visible"
title="计费规则详情"
width="720px"
append-to-body
destroy-on-close
>
<template v-if="billingRuleDialog.rule">
<div class="dialog-section-title">计费规则</div>
<el-descriptions :column="2" border class="pre-settlement-editor__billing-rule-detail">
<el-descriptions-item label="规则名称">
{{ displayValue(billingRuleDialog.rule.name) }}
</el-descriptions-item>
<el-descriptions-item label="费用类型">
{{ displayValue(billingRuleDialog.rule.feeType) }}
</el-descriptions-item>
<el-descriptions-item label="费用项">
{{ displayValue(billingRuleDialog.rule.feeItem) }}
</el-descriptions-item>
<el-descriptions-item label="计费要素">
{{ displayValue(billingRuleDialog.rule.billingElement) }}
</el-descriptions-item>
<el-descriptions-item label="计费类型">
{{ displayValue(billingRuleDialog.rule.billingType) }}
</el-descriptions-item>
<el-descriptions-item label="计费单位">
{{ displayValue(billingRuleDialog.rule.billingUnit) }}
</el-descriptions-item>
<el-descriptions-item label="单价">
{{ displayValue(billingRuleDialog.rule.unitPrice) }}
</el-descriptions-item>
<el-descriptions-item label="最低计费量">
{{ displayValue(billingRuleDialog.rule.minimumBillingWeight) }}
</el-descriptions-item>
<el-descriptions-item label="计费区间">
{{ billingRuleRanges(billingRuleDialog.rule) }}
</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">
{{ displayValue(billingRuleDialog.rule.remark) }}
</el-descriptions-item>
</el-descriptions>
<div class="dialog-section-title pre-settlement-editor__billing-rule-match-title">
匹配条件
</div>
<el-descriptions :column="2" border class="pre-settlement-editor__billing-rule-detail">
<el-descriptions-item label="起运地">
{{ displayValue(billingRuleDialog.rule.matchCondition.origin) }}
</el-descriptions-item>
<el-descriptions-item label="目的地">
{{ displayValue(billingRuleDialog.rule.matchCondition.destination) }}
</el-descriptions-item>
<el-descriptions-item label="运输方式">
{{ displayValue(billingRuleDialog.rule.matchCondition.transportMode) }}
</el-descriptions-item>
<el-descriptions-item label="货物类型">
{{ displayValue(billingRuleDialog.rule.matchCondition.cargoType) }}
</el-descriptions-item>
</el-descriptions>
</template>
<el-empty v-else description="暂无计费规则详情" />
<template #footer>
<el-button type="primary" @click="billingRuleDialog.visible = false">关闭</el-button>
</template>
</el-dialog>
</template>
<script>
import { mapGetters } from 'vuex';
import { InfoFilled } from '@element-plus/icons-vue';
import {
adjustDetail,
getCandidateDetails,
@@ -814,6 +884,7 @@ import * as XLSX from 'xlsx';
export default {
name: 'PreSettlementEditor',
components: { InfoFilled },
props: {
modelValue: {
type: Boolean,
@@ -835,6 +906,10 @@ export default {
type: Object,
default: null,
},
deferSave: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue', 'success'],
data() {
@@ -936,6 +1011,10 @@ export default {
reason: '',
},
adjustRows: [],
billingRuleDialog: {
visible: false,
rule: null,
},
attachmentFileTypes: [
'pdf',
'bmp',
@@ -1146,6 +1225,9 @@ export default {
this.changeRecords = [];
this.attachments = [];
this.selectedAttachmentRows = [];
this.adjustRows = [];
this.billingRuleDialog.visible = false;
this.billingRuleDialog.rule = null;
this.contractOptions = [];
this.contractDialog.visible = false;
this.contractDialog.expanded = false;
@@ -1421,6 +1503,7 @@ export default {
this.summaryFees.splice(index, 1);
},
recalculateSummaryRow(row) {
if (Number(row.manualFlag) !== 1) return;
row.settlementAmount = Number(row.originalAmount || 0) + Number(row.adjustAmount || 0);
},
buildSummaryFeesFromDetails() {
@@ -1548,6 +1631,13 @@ export default {
}
});
this.candidateDialog.confirming = true;
if (this.deferSave) {
this.buildSummaryFeesFromDetails();
this.candidateDialog.visible = false;
this.candidateDialog.confirming = false;
this.$message.success('结算明细添加成功,请点击保存提交');
return;
}
try {
const { data } = await save(this.buildSavePayload());
this.form.id = data?.data || this.form.id;
@@ -1588,6 +1678,10 @@ export default {
const isPersistedDetail =
row.id && sourceDetailId && String(row.id) !== String(sourceDetailId);
if (isPersistedDetail) return row;
if (this.deferSave) {
this.$message.warning('请先保存预结算单后再调整结算明细');
return null;
}
await this.$refs.formRef?.validate();
this.loading = true;
@@ -1621,11 +1715,97 @@ export default {
this.adjustRows = (data?.data || []).map(item => ({
...item,
feeItems: this.parseFeeItems(item.feeItemsJson),
billingRule: this.normalizeBillingRule(item),
}));
} finally {
this.adjustDialog.loading = false;
}
},
billingRuleName(row) {
return this.normalizeBillingRule(row).name;
},
openBillingRuleInfo(row) {
this.billingRuleDialog.rule = this.normalizeBillingRule(row);
this.billingRuleDialog.visible = true;
},
normalizeBillingRule(row) {
const source = row || {};
const nested = [
source.billingRule,
source.billingRuleInfo,
source.billingRuleJson,
source.billingRulesJson,
source.billingRules,
source.billingPlanRule,
source.feeRule,
source.rule,
].find(value => value !== undefined && value !== null && value !== '');
const parsedNested = this.parseJsonValue(nested);
const matchedRules = Array.isArray(parsedNested)
? parsedNested.filter(item => item && typeof item === 'object')
: [];
let rule = this.parseObject(matchedRules[0] || parsedNested || nested);
if (!Object.keys(rule).length && typeof nested === 'string') rule = { name: nested };
const matchCondition = this.parseObject(
source.matchCondition ?? source.billingMatchCondition ?? rule.matchCondition
);
const ruleNames = matchedRules
.map(item => item.ruleName || item.name || item.billingRuleName || item.feeItem)
.filter(Boolean);
const name =
source.billingRuleName ||
source.feeRuleName ||
source.ruleName ||
source.billingPlanRuleName ||
source.billingPlanName ||
rule.name ||
rule.ruleName ||
rule.billingRuleName ||
rule.planName ||
rule.billingPlanName ||
ruleNames.join('、') ||
rule.feeItem ||
'';
return {
...rule,
name,
feeType: source.billingFeeType || source.feeType || rule.feeType,
feeItem: source.billingFeeItem || source.feeItem || rule.feeItem,
billingElement:
source.billingElement ||
source.billingFactor ||
rule.billingElement ||
rule.billingFactor,
billingType: source.billingType || rule.billingType,
billingUnit: source.billingUnit || source.priceUnit || rule.billingUnit || rule.priceUnit,
unitPrice: source.billingUnitPrice ?? source.unitPrice ?? rule.unitPrice,
minimumBillingWeight:
source.minimumBillingWeight ?? rule.minimumBillingWeight ?? rule.lowerLimit,
remark: source.billingRuleRemark || source.ruleRemark || rule.remark,
rules: matchedRules,
matchCondition: {
...matchCondition,
origin: matchCondition.origin || matchCondition.originName,
destination: matchCondition.destination || matchCondition.destinationName,
transportMode: matchCondition.transportMode || matchCondition.transportType,
cargoType: matchCondition.cargoType || matchCondition.cargoTypeName,
},
};
},
billingRuleRanges(rule) {
const ranges = Array.isArray(rule?.limitRanges) ? rule.limitRanges : [];
if (ranges.length) {
return ranges
.map(
item => `${item.lowerLimit ?? '-'}~${item.upperLimit ?? '-'}${item.unitPrice ?? '-'}`
)
.join('');
}
if (rule?.lowerLimit !== undefined || rule?.upperLimit !== undefined) {
return `${rule.lowerLimit ?? '-'}~${rule.upperLimit ?? '-'}`;
}
return '-';
},
recalculateAdjustRow(row, changedField) {
if (changedField && changedField !== 'freight' && this.isFreightFeeItem(changedField)) {
row.freightAmount = Number(row.feeItems[changedField] || 0);
@@ -1797,6 +1977,25 @@ export default {
return {};
}
},
parseObject(value) {
if (!value) return {};
if (typeof value === 'object' && !Array.isArray(value)) return { ...value };
if (typeof value !== 'string') return {};
try {
const parsed = JSON.parse(value);
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
} catch (error) {
return {};
}
},
parseJsonValue(value) {
if (typeof value !== 'string') return value;
try {
return JSON.parse(value);
} catch (error) {
return value;
}
},
formatMoney(value, currency = 'RMB') {
if (value === undefined || value === null || value === '') return '-';
const amount = Number(value);
@@ -1983,6 +2182,28 @@ export default {
margin-top: 16px;
}
&__billing-rule-cell {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
max-width: 100%;
}
&__billing-rule-icon {
flex: 0 0 auto;
color: var(--el-color-primary);
cursor: pointer;
}
&__billing-rule-detail {
margin-top: 12px;
}
&__billing-rule-match-title {
margin-top: 20px;
}
.form-readonly {
min-height: 32px;
line-height: 32px;
@@ -6,6 +6,7 @@
page-mode
:record-id="recordId"
:initial-data="transferPayload"
:defer-save="Boolean(transferPayload)"
@success="handleSuccess"
/>
</basic-container>
@@ -699,6 +699,7 @@
append-to-body
destroy-on-close
class="settlement-detail-page__contract-dialog"
@opened="layoutGenerateContractTable"
>
<el-form
:model="generateContractDialog.query"
@@ -813,6 +814,7 @@
</el-form-item>
</el-form>
<el-table
ref="generateContractTable"
v-loading="generateContractDialog.loading"
:data="generateContractDialog.rows"
border
@@ -1454,10 +1456,16 @@ export default {
const data = this.unwrapPage(res);
this.generateContractDialog.rows = data.records || [];
this.generateContractDialog.page.total = Number(data.total || 0);
this.layoutGenerateContractTable();
} finally {
this.generateContractDialog.loading = false;
}
},
layoutGenerateContractTable() {
this.$nextTick(() => {
this.$refs.generateContractTable?.doLayout?.();
});
},
searchGenerateContracts() {
this.generateContractDialog.page.current = 1;
this.loadGenerateContracts();