✨ 结算付款体验优化与合同/运单导入校验
付款申请汇票筛选与提示去重、结算菜单拆分与金蝶同步弹框、应收应付详情高度、合同计费与半月结调整,以及导入运单批量删除校验。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -702,7 +702,7 @@
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="candidate.visible" title="选择预结算单" width="88%" append-to-body>
|
||||
<el-dialog v-model="candidate.visible" title="选择预付结算单" width="88%" append-to-body>
|
||||
<el-card>
|
||||
<el-form
|
||||
:model="candidate.query"
|
||||
@@ -711,7 +711,7 @@
|
||||
label-width="88px"
|
||||
class="formal-editor__candidate-search"
|
||||
>
|
||||
<el-form-item label="预结算单号"
|
||||
<el-form-item label="预付结算单号"
|
||||
><el-input v-model="candidate.query.preSettlementNo" clearable
|
||||
/></el-form-item>
|
||||
<el-form-item label="合同编号"
|
||||
@@ -836,20 +836,6 @@
|
||||
<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="formal-editor__billing-rule-cell">
|
||||
<span>{{ displayValue(billingRuleName(row)) }}</span>
|
||||
<el-icon
|
||||
class="formal-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 }">
|
||||
<el-input-number
|
||||
@@ -887,11 +873,29 @@
|
||||
v-for="feeItem in adjustFeeItemNames"
|
||||
:key="feeItem"
|
||||
:label="feeItem"
|
||||
min-width="140"
|
||||
min-width="160"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div v-if="isFreightFeeItem(feeItem)" class="formal-editor__billing-rule-cell">
|
||||
<el-input-number
|
||||
v-model="row.feeItems[feeItem]"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
@change="recalculateAdjustRow(row, feeItem)"
|
||||
/>
|
||||
<el-icon
|
||||
v-if="hasBillingRule(row)"
|
||||
class="formal-editor__billing-rule-icon"
|
||||
title="查看计费规则详情"
|
||||
@click="openBillingRuleInfo(row)"
|
||||
>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-input-number
|
||||
v-else
|
||||
v-model="row.feeItems[feeItem]"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
@@ -905,11 +909,6 @@
|
||||
<el-input-number v-model="row.settlementAmountTax" :precision="2" :controls="false" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" maxlength="200" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-form
|
||||
:model="adjust"
|
||||
@@ -917,8 +916,15 @@
|
||||
label-width="auto"
|
||||
class="formal-editor__adjust-reason"
|
||||
>
|
||||
<el-form-item label="调整原因" required>
|
||||
<el-input v-model="adjust.reason" maxlength="200" show-word-limit />
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="adjust.reason"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -939,51 +945,45 @@
|
||||
<template v-if="billingRuleDialog.rule">
|
||||
<div class="dialog-section-title">计费规则</div>
|
||||
<el-descriptions :column="2" border class="formal-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-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 formal-editor__billing-rule-match-title">匹配条件</div>
|
||||
<el-descriptions :column="2" border class="formal-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-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.cargoName) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="货物类型">
|
||||
{{ displayValue(billingRuleDialog.rule.matchCondition.cargoType) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
<el-empty v-else description="暂无计费规则详情" />
|
||||
@@ -995,6 +995,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import {
|
||||
adjustDetail,
|
||||
@@ -1034,7 +1035,6 @@ import {
|
||||
} from '@/option/settlement/formalSettlementTable';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { h } from 'vue';
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { formatContractOptionLabel } from '@/utils/contract';
|
||||
import { normalizeCurrencyCode } from '@/utils/currency';
|
||||
@@ -1220,7 +1220,13 @@ export default {
|
||||
return isAdministrator(this.userInfo);
|
||||
},
|
||||
title() {
|
||||
return this.readonly ? '查看正式结算单' : this.recordId ? '编辑正式结算单' : '新增正式结算单';
|
||||
const billLabel =
|
||||
this.form.settlementType === 'receivable' ? '收款结算单' : '付款结算单';
|
||||
return this.readonly
|
||||
? `查看${billLabel}`
|
||||
: this.recordId
|
||||
? `编辑${billLabel}`
|
||||
: `新增${billLabel}`;
|
||||
},
|
||||
detailFeeItemNames() {
|
||||
const names = new Set();
|
||||
@@ -1314,7 +1320,17 @@ export default {
|
||||
methods: {
|
||||
formatContractOptionLabel,
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission?.[code], false);
|
||||
if (this.isAdmin) return true;
|
||||
const action = String(code || '').replace(
|
||||
/^(payment_settlement_|receipt_settlement_|formal_settlement_)/,
|
||||
''
|
||||
);
|
||||
const prefix =
|
||||
this.form.settlementType === 'receivable' ? 'receipt_settlement' : 'payment_settlement';
|
||||
return (
|
||||
this.validData(this.permission?.[`${prefix}_${action}`], false) ||
|
||||
this.validData(this.permission?.[`formal_settlement_${action}`], false)
|
||||
);
|
||||
},
|
||||
async loadFormalDetail(id) {
|
||||
const response = this.publicMode
|
||||
@@ -2152,8 +2168,19 @@ export default {
|
||||
)}.xlsx`
|
||||
);
|
||||
},
|
||||
billingRuleName(row) {
|
||||
return this.normalizeBillingRule(row).name;
|
||||
hasBillingRule(row) {
|
||||
const rule = this.normalizeBillingRule(row);
|
||||
return Boolean(
|
||||
rule.feeItem ||
|
||||
rule.billingElement ||
|
||||
rule.billingType ||
|
||||
rule.unitPrice ||
|
||||
rule.rules?.length ||
|
||||
rule.matchCondition?.origin ||
|
||||
rule.matchCondition?.destination ||
|
||||
rule.matchCondition?.cargoName ||
|
||||
rule.matchCondition?.cargoType
|
||||
);
|
||||
},
|
||||
openBillingRuleInfo(row) {
|
||||
this.billingRuleDialog.rule = this.normalizeBillingRule(row);
|
||||
@@ -2179,26 +2206,8 @@ export default {
|
||||
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);
|
||||
return {
|
||||
...rule,
|
||||
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 ||
|
||||
'',
|
||||
feeType: source.billingFeeType || source.feeType || rule.feeType,
|
||||
feeItem: source.billingFeeItem || source.feeItem || rule.feeItem,
|
||||
billingElement:
|
||||
source.billingElement ||
|
||||
@@ -2216,7 +2225,11 @@ export default {
|
||||
...matchCondition,
|
||||
origin: matchCondition.origin || matchCondition.originName,
|
||||
destination: matchCondition.destination || matchCondition.destinationName,
|
||||
transportMode: matchCondition.transportMode || matchCondition.transportType,
|
||||
cargoName:
|
||||
matchCondition.cargoName ||
|
||||
matchCondition.cargoNameLabel ||
|
||||
source.cargoName ||
|
||||
rule.cargoName,
|
||||
cargoType: matchCondition.cargoType || matchCondition.cargoTypeName,
|
||||
},
|
||||
};
|
||||
@@ -2294,7 +2307,11 @@ export default {
|
||||
detailId: row.formalSettlementId ? row.id : null,
|
||||
sourceDetailId: row.sourceDetailId || null,
|
||||
feeKey,
|
||||
reason: pending?.reason || '',
|
||||
// 底部备注回显结算明细备注;暂存过的调整优先取暂存备注
|
||||
reason:
|
||||
pending?.reason !== undefined && pending?.reason !== null
|
||||
? pending.reason
|
||||
: row.remark || '',
|
||||
rows: [],
|
||||
targetRow: row.formalSettlementId ? null : row,
|
||||
};
|
||||
@@ -2317,7 +2334,6 @@ export default {
|
||||
}
|
||||
},
|
||||
async saveAdjustment() {
|
||||
if (!this.adjust.reason.trim()) return this.$message.warning('请输入调整原因');
|
||||
if (this.adjust.rows.some(row => row.calculating)) {
|
||||
return this.$message.warning('费用正在重新计算,请稍候');
|
||||
}
|
||||
@@ -2346,6 +2362,14 @@ export default {
|
||||
.reduce((total, item) => total + Number(item.settlementAmountTax || 0), 0)
|
||||
.toFixed(2)
|
||||
);
|
||||
const settlementAmountNoTax = Number(
|
||||
this.adjust.rows
|
||||
.reduce((total, item) => total + Number(item.settlementAmountNoTax || 0), 0)
|
||||
.toFixed(2)
|
||||
);
|
||||
const hasSettlementAmountNoTax = this.adjust.rows.some(
|
||||
item => item.settlementAmountNoTax !== undefined && item.settlementAmountNoTax !== null
|
||||
);
|
||||
const feeItems = this.adjust.rows.reduce((result, item) => {
|
||||
Object.entries(item.feeItems || {}).forEach(([name, amount]) => {
|
||||
result[name] = Number((Number(result[name] || 0) + Number(amount || 0)).toFixed(2));
|
||||
@@ -2359,24 +2383,37 @@ export default {
|
||||
originalAmount,
|
||||
adjustAmount: Number((settlementAmount - originalAmount).toFixed(2)),
|
||||
settlementAmountTax: settlementAmount,
|
||||
settlementAmountNoTax: hasSettlementAmountNoTax
|
||||
? settlementAmountNoTax
|
||||
: target.settlementAmountNoTax,
|
||||
feeItemsJson: JSON.stringify(feeItems),
|
||||
freightAmount: this.adjust.rows.reduce(
|
||||
(total, item) => total + Number(item.freightAmount || 0),
|
||||
0
|
||||
freightAmount: Number(
|
||||
this.adjust.rows
|
||||
.reduce((total, item) => total + Number(item.freightAmount || 0), 0)
|
||||
.toFixed(2)
|
||||
),
|
||||
transportQuantity: this.adjust.rows.reduce(
|
||||
(total, item) => total + Number(item.transportQuantity || 0),
|
||||
0
|
||||
transportQuantity: Number(
|
||||
this.adjust.rows
|
||||
.reduce((total, item) => total + Number(item.transportQuantity || 0), 0)
|
||||
.toFixed(2)
|
||||
),
|
||||
mileage: Number(
|
||||
this.adjust.rows
|
||||
.reduce((total, item) => total + Number(item.mileage || 0), 0)
|
||||
.toFixed(2)
|
||||
),
|
||||
mileage: this.adjust.rows.reduce((total, item) => total + Number(item.mileage || 0), 0),
|
||||
unitPrice: edited.unitPrice,
|
||||
remark: edited.remark,
|
||||
// 底部备注写入结算明细备注
|
||||
remark: this.adjust.reason || '',
|
||||
pendingDetailAdjustment: true,
|
||||
});
|
||||
if (this.adjust.feeKey) {
|
||||
const clonedRows = this.cloneAdjustRows(this.adjust.rows);
|
||||
// 暂存后同步费用快照,避免再次打开调整弹窗或汇总时仍用调整前数据。
|
||||
this.detailFeeSnapshots[this.adjust.feeKey] = clonedRows;
|
||||
this.pendingAdjustments[this.adjust.feeKey] = {
|
||||
reason: this.adjust.reason,
|
||||
rows: this.cloneAdjustRows(this.adjust.rows),
|
||||
rows: clonedRows,
|
||||
};
|
||||
}
|
||||
await this.refreshSummaryFees();
|
||||
@@ -2398,7 +2435,6 @@ export default {
|
||||
feeItems: row.feeItems,
|
||||
settlementAmountTax: row.settlementAmountTax,
|
||||
settlementAmountNoTax: row.settlementAmountNoTax,
|
||||
remark: row.remark,
|
||||
})),
|
||||
});
|
||||
this.$message.success('调整保存成功');
|
||||
@@ -2422,6 +2458,17 @@ export default {
|
||||
}));
|
||||
await this.confirmInvoiceClaims();
|
||||
},
|
||||
// 发票认领默认金额以当前结算金额为准:优先表单结算金额,其次结算合计,再次明细含税合计。
|
||||
resolveInvoiceClaimSettlementAmount() {
|
||||
const formAmount = Number(this.form.settlementAmount || 0);
|
||||
if (formAmount > 0) return formAmount;
|
||||
const summaryAmount = Number(this.summaryTotal || 0);
|
||||
if (summaryAmount > 0) return summaryAmount;
|
||||
return this.details.reduce(
|
||||
(total, row) => total + Number(row.settlementAmountTax || row.settlementAmount || 0),
|
||||
0
|
||||
);
|
||||
},
|
||||
async confirmInvoiceClaims() {
|
||||
if (!this.invoiceClaimDialog.selected.length) {
|
||||
this.$message.warning('请至少选择一条发票');
|
||||
@@ -2430,26 +2477,33 @@ export default {
|
||||
this.invoiceClaimDialog.confirming = true;
|
||||
try {
|
||||
const existingNumbers = new Set(this.invoices.map(item => String(item.invoiceNo)));
|
||||
const settlementAmount = this.resolveInvoiceClaimSettlementAmount();
|
||||
if (settlementAmount > 0 && Number(this.form.settlementAmount || 0) <= 0) {
|
||||
this.form.settlementAmount = Number(settlementAmount).toFixed(2);
|
||||
}
|
||||
let remaining = Math.max(
|
||||
Number(this.form.settlementAmount || 0) -
|
||||
settlementAmount -
|
||||
this.invoices.reduce((total, item) => total + Number(item.matchedAmount || 0), 0),
|
||||
0
|
||||
);
|
||||
const invoices = this.invoiceClaimDialog.selected
|
||||
.filter(row => !existingNumbers.has(String(row.invoiceNo)))
|
||||
.map(row => {
|
||||
const sourceAmount = Number(row.matchedAmount || 0);
|
||||
// 票池 matchedAmount 默认等于整张发票金额;可匹配口径优先取 availableInvoiceAmount
|
||||
const sourceAmount = Number(
|
||||
row.availableInvoiceAmount || row.matchedAmount || row.invoiceAmount || 0
|
||||
);
|
||||
// 可匹配金额大于剩余结算金额时,默认匹配金额取剩余结算金额(单张发票常见场景)
|
||||
const matchedAmount =
|
||||
Number(this.form.settlementAmount || 0) > 0
|
||||
? Math.min(sourceAmount, remaining)
|
||||
: sourceAmount;
|
||||
if (Number(this.form.settlementAmount || 0) > 0) {
|
||||
settlementAmount > 0 ? Math.min(sourceAmount, remaining) : sourceAmount;
|
||||
if (settlementAmount > 0) {
|
||||
remaining = Math.max(0, remaining - matchedAmount);
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
invoiceType: row.invoiceTypeName || row.invoiceType || '',
|
||||
matchedAmount,
|
||||
availableInvoiceAmount: sourceAmount,
|
||||
matchedAmount: Number(matchedAmount.toFixed(2)),
|
||||
};
|
||||
})
|
||||
.filter(row => row.matchedAmount > 0);
|
||||
@@ -2507,7 +2561,7 @@ export default {
|
||||
}
|
||||
matchedTotal += matchedAmount;
|
||||
}
|
||||
if (matchedTotal > Number(this.form.settlementAmount || 0)) {
|
||||
if (matchedTotal > this.resolveInvoiceClaimSettlementAmount()) {
|
||||
this.$message.warning('发票匹配结算单金额合计不能超过结算金额');
|
||||
return false;
|
||||
}
|
||||
@@ -2615,7 +2669,7 @@ export default {
|
||||
summaryFees: this.summaryFees
|
||||
.filter(
|
||||
row =>
|
||||
row.manualFlag === 1 ||
|
||||
Number(row.manualFlag) === 1 ||
|
||||
row.id ||
|
||||
Number(row.adjustAmount || 0) !== 0 ||
|
||||
String(row.remark || '').trim()
|
||||
@@ -2626,7 +2680,7 @@ export default {
|
||||
feeItem: row.feeItem,
|
||||
adjustAmount: Number(row.adjustAmount || 0),
|
||||
remark: row.remark,
|
||||
manualFlag: row.manualFlag || 0,
|
||||
manualFlag: Number(row.manualFlag) === 1 ? 1 : 0,
|
||||
})),
|
||||
attachmentsJson: this.stringifyAttachments(this.attachments),
|
||||
invoices: this.invoices.map(row => ({
|
||||
@@ -3053,20 +3107,6 @@ export default {
|
||||
.formal-editor__billing-rule-match-title {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.dialog-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 22px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.dialog-section-title::before {
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
margin-right: 8px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
.formal-editor__attachment-missing {
|
||||
margin-left: 12px;
|
||||
color: var(--el-color-danger);
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<div class="fs-table-panel__toolbar">
|
||||
<div>
|
||||
<el-button
|
||||
v-if="hasPermission('formal_settlement_add')"
|
||||
v-if="hasPermission('add')"
|
||||
type="primary"
|
||||
@click="$emit('create')"
|
||||
>新增</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('formal_settlement_payment')"
|
||||
v-if="showPaymentAction && hasPermission('payment')"
|
||||
type="primary"
|
||||
plain
|
||||
:disabled="!selection.length"
|
||||
@@ -17,23 +17,23 @@
|
||||
>付款申请</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('formal_settlement_sync')"
|
||||
v-if="hasPermission('sync')"
|
||||
type="primary"
|
||||
plain
|
||||
:disabled="selection.length !== 1"
|
||||
:disabled="!selection.length"
|
||||
:loading="syncLoading"
|
||||
@click="$emit('sync')"
|
||||
>同步金蝶</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('formal_settlement_print')"
|
||||
v-if="hasPermission('print')"
|
||||
type="primary"
|
||||
plain
|
||||
@click="emitSingle('print')"
|
||||
>打印结算单</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('formal_settlement_export')"
|
||||
v-if="hasPermission('export')"
|
||||
type="primary"
|
||||
plain
|
||||
@click="$emit('export')"
|
||||
@@ -149,6 +149,9 @@ export default {
|
||||
hasPermission: { type: Function, required: true },
|
||||
mkEnabled: { type: Boolean, default: false },
|
||||
searchVisible: { type: Boolean, default: true },
|
||||
/** 是否展示付款申请按钮(仅付款结算单) */
|
||||
showPaymentAction: { type: Boolean, default: true },
|
||||
billLabel: { type: String, default: '结算单' },
|
||||
},
|
||||
emits: [
|
||||
'create',
|
||||
@@ -223,7 +226,7 @@ export default {
|
||||
},
|
||||
emitSingle(type) {
|
||||
if (this.selection.length !== 1) {
|
||||
this.$message.warning(`${type === 'print' ? '打印' : '操作'}需选择一条正式结算单`);
|
||||
this.$message.warning(`${type === 'print' ? '打印' : '操作'}需选择一条${this.billLabel}`);
|
||||
return;
|
||||
}
|
||||
this.$emit('action', { type, row: this.selection[0] });
|
||||
@@ -233,27 +236,26 @@ export default {
|
||||
},
|
||||
rowActions(row) {
|
||||
const actions = [];
|
||||
if (this.hasPermission('formal_settlement_view'))
|
||||
actions.push({ type: 'view', label: '查看' });
|
||||
if (this.hasPermission('formal_settlement_edit') && this.isEditable(row))
|
||||
if (this.hasPermission('view')) actions.push({ type: 'view', label: '查看' });
|
||||
if (this.hasPermission('edit') && this.isEditable(row))
|
||||
actions.push({ type: 'edit', label: '编辑' });
|
||||
if (this.hasPermission('formal_settlement_delete') && row.approvalStatus === 'draft')
|
||||
if (this.hasPermission('delete') && row.approvalStatus === 'draft')
|
||||
actions.push({ type: 'delete', label: '删除', danger: true });
|
||||
if (this.hasPermission('formal_settlement_submit') && this.isEditable(row))
|
||||
if (this.hasPermission('submit') && this.isEditable(row))
|
||||
actions.push({ type: 'submit', label: '提交' });
|
||||
if (
|
||||
this.mkEnabled &&
|
||||
this.hasPermission('formal_settlement_approve') &&
|
||||
this.hasPermission('approve') &&
|
||||
['reviewing', 'approved'].includes(row.approvalStatus)
|
||||
) {
|
||||
actions.push({ type: 'mkAudit', label: '流程' });
|
||||
}
|
||||
if (!this.mkEnabled && this.hasPermission('formal_settlement_approve') && row.approvalStatus === 'reviewing') {
|
||||
if (!this.mkEnabled && this.hasPermission('approve') && row.approvalStatus === 'reviewing') {
|
||||
actions.push({ type: 'approve', label: '通过' });
|
||||
actions.push({ type: 'return', label: '驳回', danger: true });
|
||||
}
|
||||
if (
|
||||
this.hasPermission('formal_settlement_void') &&
|
||||
this.hasPermission('void') &&
|
||||
row.approvalStatus === 'approved' &&
|
||||
row.kingdeeSyncStatus !== 'synced'
|
||||
)
|
||||
|
||||
@@ -459,7 +459,7 @@
|
||||
</section-card>
|
||||
|
||||
<section-card v-if="form.id && readonly" title="变更记录">
|
||||
<el-table :data="adjustChangeRecords" border>
|
||||
<el-table :data="changeRecords" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in changeTableColumns"
|
||||
@@ -753,20 +753,6 @@
|
||||
<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>
|
||||
@@ -809,11 +795,33 @@
|
||||
v-for="feeItem in adjustFeeItemNames"
|
||||
:key="feeItem"
|
||||
:label="feeItem"
|
||||
min-width="140"
|
||||
min-width="160"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div
|
||||
v-if="isFreightFeeItem(feeItem)"
|
||||
class="pre-settlement-editor__billing-rule-cell"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="row.feeItems[feeItem]"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
@change="recalculateAdjustRow(row, feeItem)"
|
||||
/>
|
||||
<el-icon
|
||||
v-if="hasBillingRule(row)"
|
||||
class="pre-settlement-editor__billing-rule-icon"
|
||||
title="查看计费规则详情"
|
||||
@click="openBillingRuleInfo(row)"
|
||||
>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-input-number
|
||||
v-else
|
||||
v-model="row.feeItems[feeItem]"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
@@ -833,11 +841,6 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" maxlength="200" :disabled="adjustDialog.readonly" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-form
|
||||
v-if="!adjustDialog.readonly"
|
||||
@@ -846,7 +849,7 @@
|
||||
label-width="auto"
|
||||
class="pre-settlement-editor__adjust-reason"
|
||||
>
|
||||
<el-form-item label="调整原因">
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="adjustDialog.reason"
|
||||
type="textarea"
|
||||
@@ -885,12 +888,6 @@
|
||||
<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>
|
||||
@@ -926,8 +923,8 @@
|
||||
<el-descriptions-item label="目的地">
|
||||
{{ displayValue(billingRuleDialog.rule.matchCondition.destination) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运输方式">
|
||||
{{ displayValue(billingRuleDialog.rule.matchCondition.transportMode) }}
|
||||
<el-descriptions-item label="货物名称">
|
||||
{{ displayValue(billingRuleDialog.rule.matchCondition.cargoName) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="货物类型">
|
||||
{{ displayValue(billingRuleDialog.rule.matchCondition.cargoType) }}
|
||||
@@ -1202,8 +1199,8 @@ export default {
|
||||
return this.editable && this.hasPermission('pre_settlement_adjust');
|
||||
},
|
||||
dialogTitle() {
|
||||
if (this.readonly) return '查看预结算单';
|
||||
return this.form.id ? '编辑预结算单' : '新增预结算单';
|
||||
if (this.readonly) return '查看预付结算单';
|
||||
return this.form.id ? '编辑预付结算单' : '新增预付结算单';
|
||||
},
|
||||
settlementTypeName() {
|
||||
return this.form.settlementType === 'receivable' ? '应收' : '应付';
|
||||
@@ -1285,13 +1282,6 @@ export default {
|
||||
});
|
||||
return Array.from(names);
|
||||
},
|
||||
adjustChangeRecords() {
|
||||
return this.changeRecords.filter(
|
||||
row =>
|
||||
row.changeType === '结算明细项' &&
|
||||
String(row.lineNo ?? '') === String(this.adjustDialog.detailLineNo ?? '')
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
@@ -1865,7 +1855,25 @@ export default {
|
||||
});
|
||||
|
||||
const manualFees = this.summaryFees.filter(row => Number(row.manualFlag) === 1);
|
||||
this.summaryFees = [...summaryMap.values(), ...manualFees];
|
||||
const nextFeeMap = new Map();
|
||||
summaryMap.forEach((row, key) => nextFeeMap.set(`0\u0000${key}`, row));
|
||||
manualFees.forEach(row => {
|
||||
const key = `1\u0000${row.feeType}\u0000${row.feeItem}`;
|
||||
nextFeeMap.set(key, row);
|
||||
});
|
||||
// 按编辑前顺序回填,新增费用项追加到末尾,避免手工费用被统一挤到最后。
|
||||
const orderedFees = [];
|
||||
const previousKeys = this.summaryFees.map(
|
||||
row => `${Number(row.manualFlag) === 1 ? 1 : 0}\u0000${row.feeType}\u0000${row.feeItem}`
|
||||
);
|
||||
previousKeys.forEach(key => {
|
||||
const row = nextFeeMap.get(key);
|
||||
if (!row) return;
|
||||
orderedFees.push(row);
|
||||
nextFeeMap.delete(key);
|
||||
});
|
||||
nextFeeMap.forEach(row => orderedFees.push(row));
|
||||
this.summaryFees = orderedFees;
|
||||
this.recalculateLocalAmount();
|
||||
},
|
||||
// 每条明细对应的费用行:[调整前基准行, 调整后当前行],缺少来源费用快照时退化为明细自身的汇总行。
|
||||
@@ -2053,7 +2061,7 @@ export default {
|
||||
}
|
||||
},
|
||||
async removeDetailRow(row) {
|
||||
await this.$confirm('确认将该明细踢出预结算单?', '提示', { type: 'warning' });
|
||||
await this.$confirm('确认将该明细踢出预付结算单?', '提示', { type: 'warning' });
|
||||
if (!this.form.id || !row.id || row.id === row.sourceDetailId) {
|
||||
this.details = this.details.filter(item => item !== row);
|
||||
this.buildSummaryFeesFromDetails();
|
||||
@@ -2228,7 +2236,11 @@ export default {
|
||||
: '';
|
||||
const sourceDetailId = String(this.adjustDialog.sourceDetailId || '');
|
||||
const pending = sourceDetailId ? this.pendingAdjustments[sourceDetailId] : null;
|
||||
this.adjustDialog.reason = pending?.reason || '';
|
||||
// 底部备注回显结算明细备注;暂存过的调整优先取暂存备注
|
||||
this.adjustDialog.reason =
|
||||
pending?.reason !== undefined && pending?.reason !== null
|
||||
? pending.reason
|
||||
: detailRow.remark || '';
|
||||
try {
|
||||
// 已暂存的调整结果优先回显,避免重新打开时又回到调整前的数据。
|
||||
if (pending?.rows?.length) {
|
||||
@@ -2295,8 +2307,19 @@ export default {
|
||||
this.adjustChangeRecordDetailRows = this.buildAdjustChangeRecordRows(row);
|
||||
this.adjustChangeRecordVisible = true;
|
||||
},
|
||||
billingRuleName(row) {
|
||||
return this.normalizeBillingRule(row).name;
|
||||
hasBillingRule(row) {
|
||||
const rule = this.normalizeBillingRule(row);
|
||||
return Boolean(
|
||||
rule.feeItem ||
|
||||
rule.billingElement ||
|
||||
rule.billingType ||
|
||||
rule.unitPrice ||
|
||||
rule.rules?.length ||
|
||||
rule.matchCondition?.origin ||
|
||||
rule.matchCondition?.destination ||
|
||||
rule.matchCondition?.cargoName ||
|
||||
rule.matchCondition?.cargoType
|
||||
);
|
||||
},
|
||||
openBillingRuleInfo(row) {
|
||||
this.billingRuleDialog.rule = this.normalizeBillingRule(row);
|
||||
@@ -2323,27 +2346,8 @@ export default {
|
||||
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 ||
|
||||
@@ -2361,7 +2365,11 @@ export default {
|
||||
...matchCondition,
|
||||
origin: matchCondition.origin || matchCondition.originName,
|
||||
destination: matchCondition.destination || matchCondition.destinationName,
|
||||
transportMode: matchCondition.transportMode || matchCondition.transportType,
|
||||
cargoName:
|
||||
matchCondition.cargoName ||
|
||||
matchCondition.cargoNameLabel ||
|
||||
source.cargoName ||
|
||||
rule.cargoName,
|
||||
cargoType: matchCondition.cargoType || matchCondition.cargoTypeName,
|
||||
},
|
||||
};
|
||||
@@ -2451,9 +2459,13 @@ export default {
|
||||
reason: this.adjustDialog.reason,
|
||||
rows: this.adjustRows.map(row => ({ ...row, feeItems: { ...row.feeItems } })),
|
||||
};
|
||||
this.applyLocalAdjustment(this.adjustDialog.sourceDetailId, this.adjustRows);
|
||||
this.applyLocalAdjustment(
|
||||
this.adjustDialog.sourceDetailId,
|
||||
this.adjustRows,
|
||||
this.adjustDialog.reason
|
||||
);
|
||||
this.adjustDialog.visible = false;
|
||||
this.$message.success('结算明细调整已暂存,保存预结算单后生效');
|
||||
this.$message.success('结算明细调整已暂存,保存预付结算单后生效');
|
||||
return;
|
||||
}
|
||||
await adjustDetail({
|
||||
@@ -2468,7 +2480,6 @@ export default {
|
||||
feeItems: row.feeItems,
|
||||
settlementAmountTax: row.settlementAmountTax,
|
||||
settlementAmountNoTax: row.settlementAmountNoTax,
|
||||
remark: row.remark,
|
||||
})),
|
||||
});
|
||||
this.adjustDialog.visible = false;
|
||||
@@ -2479,12 +2490,12 @@ export default {
|
||||
this.adjustDialog.saving = false;
|
||||
}
|
||||
},
|
||||
applyLocalAdjustment(sourceDetailId, rows) {
|
||||
applyLocalAdjustment(sourceDetailId, rows, detailRemark) {
|
||||
const detail = this.details.find(
|
||||
item => String(item.sourceDetailId || item.id) === String(sourceDetailId)
|
||||
);
|
||||
if (!detail) return;
|
||||
// 原金额以进入预结算单时的费用快照为基准,调整金额 = 结算金额 - 原金额。
|
||||
// 原金额以进入预付结算单时的费用快照为基准,调整金额 = 结算金额 - 原金额。
|
||||
const baseRows = this.sourceFeeSnapshots[String(sourceDetailId)] || [];
|
||||
const originalAmount = baseRows.length
|
||||
? baseRows.reduce((sum, row) => sum + Number(row.settlementAmountTax || 0), 0)
|
||||
@@ -2493,20 +2504,48 @@ export default {
|
||||
(sum, row) => sum + Number(row.settlementAmountTax || 0),
|
||||
0
|
||||
);
|
||||
const settlementAmountNoTax = rows.reduce(
|
||||
(sum, row) => sum + Number(row.settlementAmountNoTax || 0),
|
||||
0
|
||||
);
|
||||
const hasSettlementAmountNoTax = rows.some(
|
||||
row => row.settlementAmountNoTax !== undefined && row.settlementAmountNoTax !== null
|
||||
);
|
||||
const freightAmount = rows.reduce((sum, row) => sum + Number(row.freightAmount || 0), 0);
|
||||
const transportQuantity = rows.reduce(
|
||||
(sum, row) => sum + Number(row.transportQuantity || 0),
|
||||
0
|
||||
);
|
||||
const mileage = rows.reduce((sum, row) => sum + Number(row.mileage || 0), 0);
|
||||
const feeItems = {};
|
||||
rows.forEach(row =>
|
||||
Object.entries(row.feeItems || {}).forEach(([name, amount]) => {
|
||||
feeItems[name] = Number((Number(feeItems[name] || 0) + Number(amount || 0)).toFixed(2));
|
||||
})
|
||||
);
|
||||
const firstRow = rows[0] || {};
|
||||
Object.assign(detail, {
|
||||
transportQuantity: Number(transportQuantity.toFixed(2)),
|
||||
mileage: Number(mileage.toFixed(2)),
|
||||
unitPrice: Number(firstRow.unitPrice || 0),
|
||||
originalAmount: Number(originalAmount.toFixed(2)),
|
||||
adjustAmount: Number((settlementAmountTax - originalAmount).toFixed(2)),
|
||||
settlementAmountTax: Number(settlementAmountTax.toFixed(2)),
|
||||
settlementAmountNoTax: hasSettlementAmountNoTax
|
||||
? Number(settlementAmountNoTax.toFixed(2))
|
||||
: detail.settlementAmountNoTax,
|
||||
freightAmount: Number(freightAmount.toFixed(2)),
|
||||
feeItemsJson: JSON.stringify(feeItems),
|
||||
// 底部备注写入结算明细备注
|
||||
remark: detailRemark ?? '',
|
||||
});
|
||||
// 暂存调整后同步费用缓存,保证货物名称/类型拼接与列表展示一致。
|
||||
if (detail.id) {
|
||||
this.detailFeeCache[detail.id] = rows.map(row => ({
|
||||
...row,
|
||||
feeItems: { ...row.feeItems },
|
||||
}));
|
||||
}
|
||||
this.buildSummaryFeesFromDetails();
|
||||
},
|
||||
async persistPendingAdjustments() {
|
||||
@@ -2534,11 +2573,14 @@ export default {
|
||||
feeItems: pendingRow.feeItems,
|
||||
settlementAmountTax: pendingRow.settlementAmountTax,
|
||||
settlementAmountNoTax: pendingRow.settlementAmountNoTax,
|
||||
remark: pendingRow.remark,
|
||||
};
|
||||
});
|
||||
if (rows.length) {
|
||||
await adjustDetail({ detailId: detail.id, changeReason: pending.reason, rows });
|
||||
await adjustDetail({
|
||||
detailId: detail.id,
|
||||
changeReason: pending.reason,
|
||||
rows,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.pendingAdjustments = {};
|
||||
@@ -2610,7 +2652,7 @@ export default {
|
||||
XLSX.utils.book_append_sheet(workbook, sheet, '结算明细');
|
||||
XLSX.writeFile(
|
||||
workbook,
|
||||
`${this.form.preSettlementNo || '预结算单'}结算明细${this.$dayjs().format(
|
||||
`${this.form.preSettlementNo || '预付结算单'}结算明细${this.$dayjs().format(
|
||||
'YYYY-MM-DD HH-mm-ss'
|
||||
)}.xlsx`
|
||||
);
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<el-option label="正式结算单" value="formal" />
|
||||
<el-option
|
||||
v-if="form.settlementType === 'payable'"
|
||||
label="预结算单"
|
||||
label="预付结算单"
|
||||
value="pre"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
@@ -508,7 +508,7 @@
|
||||
<span class="completion-dialog__value">{{ displayValue(form.reconciliationNo) }}</span>
|
||||
</div>
|
||||
<div class="completion-dialog__field">
|
||||
<span class="completion-dialog__label">预结算单号</span>
|
||||
<span class="completion-dialog__label">预付结算单号</span>
|
||||
<span class="completion-dialog__value">{{
|
||||
displayValue(form.preSettlementNo || form.formalSettlementNo)
|
||||
}}</span>
|
||||
|
||||
Reference in New Issue
Block a user