Merge remote-tracking branch 'origin/master' into feature/bfi-api-rate
# Conflicts: # src/views/vehicle/customer-archive.vue
This commit is contained in:
@@ -212,8 +212,15 @@
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-show="!detailCollapsed" :data="filteredDetails" border>
|
||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||
<el-table v-show="!detailCollapsed" :data="pagedDetails" border>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
width="64"
|
||||
fixed="left"
|
||||
align="center"
|
||||
:index="detailIndexMethod"
|
||||
/>
|
||||
<el-table-column
|
||||
v-for="column in detailTableColumns"
|
||||
:key="column.prop"
|
||||
@@ -246,6 +253,16 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-show="!detailCollapsed" class="formal-editor__pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="detailPage.current"
|
||||
v-model:page-size="detailPage.size"
|
||||
:total="filteredDetails.length"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleDetailPageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</section-card>
|
||||
|
||||
<section-card>
|
||||
@@ -573,7 +590,7 @@
|
||||
>提交</el-button
|
||||
>
|
||||
</template>
|
||||
<div v-if="pageMode" class="formal-editor__page-actions">
|
||||
<div v-if="pageMode && !publicMode" class="formal-editor__page-actions">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button
|
||||
v-if="editable && !recordId"
|
||||
@@ -879,6 +896,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import {
|
||||
adjustDetail,
|
||||
getCandidateDetails,
|
||||
@@ -930,6 +948,10 @@ export default {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
publicMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'success'],
|
||||
data() {
|
||||
@@ -1038,6 +1060,10 @@ export default {
|
||||
batchNo: '',
|
||||
cargoName: '',
|
||||
},
|
||||
detailPage: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -1085,6 +1111,13 @@ export default {
|
||||
})
|
||||
);
|
||||
},
|
||||
pagedDetails() {
|
||||
const list = this.filteredDetails;
|
||||
const size = Number(this.detailPage.size) || 10;
|
||||
const current = Math.max(Number(this.detailPage.current) || 1, 1);
|
||||
const start = (current - 1) * size;
|
||||
return list.slice(start, start + size);
|
||||
},
|
||||
missingAttachmentTypeText() {
|
||||
const missing = this.getMissingAttachmentTypes();
|
||||
return missing.length ? `未上传:${missing.join('、')}` : '';
|
||||
@@ -1104,6 +1137,16 @@ export default {
|
||||
if (value) this.initialize();
|
||||
},
|
||||
},
|
||||
filteredDetails: {
|
||||
handler(list) {
|
||||
const size = Number(this.detailPage.size) || 10;
|
||||
const maxPage = Math.max(1, Math.ceil((list?.length || 0) / size) || 1);
|
||||
if (this.detailPage.current > maxPage) {
|
||||
this.detailPage.current = maxPage;
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
summaryTotal(value) {
|
||||
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
||||
this.form.localSettlementAmount = (
|
||||
@@ -1118,7 +1161,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async loadFormalDetail(id) {
|
||||
const response = await getDetail(id);
|
||||
const response = this.publicMode
|
||||
? await getMkPublicDetail('formal-settlement', id)
|
||||
: await getDetail(id);
|
||||
const data = this.unwrapData(response);
|
||||
this.form = {
|
||||
...createFormalSettlementForm(),
|
||||
@@ -1130,12 +1175,13 @@ export default {
|
||||
};
|
||||
this.sources = data.sources || [];
|
||||
this.details = data.details || [];
|
||||
this.detailPage.current = 1;
|
||||
this.summaryFees = data.summaryFees || [];
|
||||
// 服务端数据已刷新,费用快照与暂存调整全部作废。
|
||||
this.detailFeeSnapshots = {};
|
||||
this.pendingAdjustments = {};
|
||||
this.paymentApplications = data.paymentApplications || [];
|
||||
if (this.readonly && this.form.settlementType === 'receivable') {
|
||||
if (this.readonly && this.form.settlementType === 'receivable' && !this.publicMode) {
|
||||
this.receiptClaims = this.unwrapData(await getReceiptClaims(id)) || [];
|
||||
}
|
||||
this.adjustments = data.adjustments || [];
|
||||
@@ -1181,14 +1227,17 @@ export default {
|
||||
rule: null,
|
||||
};
|
||||
this.detailCollapsed = false;
|
||||
this.detailPage = { current: 1, size: 10 };
|
||||
this.resetDetailQuery(false);
|
||||
await Promise.all([
|
||||
this.loadAllContracts(),
|
||||
this.loadFeeOptions(),
|
||||
this.loadFeeCategoryOptions(),
|
||||
this.loadAttachmentTypeOptions(),
|
||||
this.loadTransportTypeOptions(),
|
||||
]);
|
||||
if (!this.publicMode) {
|
||||
await Promise.all([
|
||||
this.loadAllContracts(),
|
||||
this.loadFeeOptions(),
|
||||
this.loadFeeCategoryOptions(),
|
||||
this.loadAttachmentTypeOptions(),
|
||||
this.loadTransportTypeOptions(),
|
||||
]);
|
||||
}
|
||||
if (!this.recordId) {
|
||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||||
if (this.initialData) await this.applyInitialData();
|
||||
@@ -1273,6 +1322,7 @@ export default {
|
||||
settlementAmountTax:
|
||||
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
|
||||
}));
|
||||
this.detailPage.current = 1;
|
||||
this.form.sourceDetailIds = this.details.map(item => item.sourceDetailId);
|
||||
await this.refreshSummaryFees();
|
||||
},
|
||||
@@ -1318,6 +1368,7 @@ export default {
|
||||
sourcePreSettlementId: settlement.id,
|
||||
}))
|
||||
);
|
||||
this.detailPage.current = 1;
|
||||
const summaryMap = new Map();
|
||||
settlements
|
||||
.flatMap(settlement => settlement.summaryFees || [])
|
||||
@@ -1564,6 +1615,7 @@ export default {
|
||||
...this.details.filter(item => item.formalSettlementId),
|
||||
...existing.values(),
|
||||
];
|
||||
this.detailPage.current = 1;
|
||||
this.form.sourceDetailIds = [...existing.values()].map(item => item.sourceDetailId);
|
||||
this.detailCandidate.loading = true;
|
||||
try {
|
||||
@@ -1782,6 +1834,9 @@ export default {
|
||||
return sourceDetailId ? `src:${sourceDetailId}` : '';
|
||||
},
|
||||
async loadDetailFeeRows(detail) {
|
||||
if (this.publicMode) {
|
||||
return [this.normalizeAdjustRow(detail)];
|
||||
}
|
||||
if (detail.formalSettlementId && detail.id) {
|
||||
const rows = this.unwrapData(await getDetailFees(detail.id)) || [];
|
||||
return rows.map(item => this.normalizeAdjustRow(item));
|
||||
@@ -1883,12 +1938,19 @@ export default {
|
||||
},
|
||||
handleDetailQuery() {
|
||||
this.appliedDetailQuery = { ...this.detailQuery };
|
||||
this.detailPage.current = 1;
|
||||
},
|
||||
resetDetailQuery(apply = true) {
|
||||
this.detailQuery = { documentNo: '', waybillNo: '', batchNo: '', cargoName: '' };
|
||||
if (apply) this.handleDetailQuery();
|
||||
else this.appliedDetailQuery = { ...this.detailQuery };
|
||||
},
|
||||
handleDetailPageSizeChange() {
|
||||
this.detailPage.current = 1;
|
||||
},
|
||||
detailIndexMethod(index) {
|
||||
return (this.detailPage.current - 1) * this.detailPage.size + index + 1;
|
||||
},
|
||||
exportDetails() {
|
||||
if (!this.details.length) return this.$message.warning('暂无可导出的结算明细');
|
||||
const rows = this.details.map((row, index) => {
|
||||
|
||||
@@ -66,6 +66,12 @@
|
||||
<span v-else-if="field.prop === 'settlementType'" class="form-readonly">
|
||||
{{ settlementTypeName }}
|
||||
</span>
|
||||
<span v-else-if="field.prop === 'preSettlementNo'" class="form-readonly">
|
||||
{{ form.preSettlementNo || (!form.id ? '系统自动生成' : '-') }}
|
||||
</span>
|
||||
<span v-else-if="field.prop === 'createTime'" class="form-readonly">
|
||||
{{ formatCreateDate(form.createTime) }}
|
||||
</span>
|
||||
<span v-else-if="field.money" class="form-readonly">
|
||||
{{ formatMoney(form[field.prop], moneyCurrency(field.prop)) }}
|
||||
</span>
|
||||
@@ -184,7 +190,6 @@
|
||||
</template>
|
||||
<el-form
|
||||
:model="detailQuery"
|
||||
inline
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="pre-settlement-editor__detail-filter"
|
||||
@@ -228,8 +233,15 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div v-show="!detailCollapsed">
|
||||
<el-table :data="filteredDetails" border class="pre-settlement-editor__detail-table">
|
||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||
<el-table :data="pagedDetails" border class="pre-settlement-editor__detail-table">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
width="64"
|
||||
fixed="left"
|
||||
align="center"
|
||||
:index="detailIndexMethod"
|
||||
/>
|
||||
<el-table-column
|
||||
v-for="column in visibleDetailColumns"
|
||||
:key="column.prop"
|
||||
@@ -300,6 +312,16 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pre-settlement-editor__pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="detailPage.current"
|
||||
v-model:page-size="detailPage.size"
|
||||
:total="filteredDetails.length"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleDetailPageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section-card>
|
||||
|
||||
@@ -313,13 +335,43 @@
|
||||
<el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column label="文件名" min-width="260" show-overflow-tooltip>
|
||||
<el-table-column label="附件类型" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-if="editable"
|
||||
v-model="row.type"
|
||||
filterable
|
||||
placeholder="请选择"
|
||||
@change="sortAttachments"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in attachmentTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<span v-else>{{ attachmentTypeName(row.type) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件名称" min-width="260" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="previewAttachment(row)">
|
||||
{{ attachmentName(row) }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="附件描述" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<span v-if="!editable">{{ row.description || '-' }}</span>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="row.description"
|
||||
maxlength="200"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件大小" width="120" align="center">
|
||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||
</el-table-column>
|
||||
@@ -425,7 +477,7 @@
|
||||
提交
|
||||
</el-button>
|
||||
</template>
|
||||
<div v-if="pageMode" class="pre-settlement-editor__page-actions">
|
||||
<div v-if="pageMode && !publicMode" 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
|
||||
@@ -681,134 +733,113 @@
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<el-tabs v-model="adjustDialog.activeTab" class="pre-settlement-editor__adjust-tabs">
|
||||
<el-tab-pane label="结算明细调整" name="adjust">
|
||||
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
|
||||
<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>
|
||||
<el-input-number
|
||||
v-else
|
||||
v-model="row.transportQuantity"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
@change="recalculateAdjustRow(row, 'transportQuantity')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="里程(KM)" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.mileage"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
@change="recalculateAdjustRow(row, 'mileage')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运输单价" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.unitPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
@change="recalculateAdjustRow(row, 'unitPrice')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="feeItem in adjustFeeItemNames"
|
||||
:key="feeItem"
|
||||
:label="feeItem"
|
||||
min-width="140"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.feeItems[feeItem]"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
@change="recalculateAdjustRow(row, feeItem)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算金额(含税)" min-width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.settlementAmountTax"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
/>
|
||||
</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"
|
||||
:model="adjustDialog"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="pre-settlement-editor__adjust-reason"
|
||||
>
|
||||
<el-form-item label="调整原因">
|
||||
<el-input v-model="adjustDialog.reason" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="变更记录" name="records">
|
||||
<el-table :data="changeRecords" border>
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="changeTime" label="变更日期" min-width="170" align="center" />
|
||||
<el-table-column prop="operatorName" label="经办人" min-width="140" align="center" />
|
||||
<el-table-column prop="changeType" label="变更类型" min-width="160" align="center" />
|
||||
<el-table-column
|
||||
prop="changeReason"
|
||||
label="变更原因"
|
||||
min-width="240"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
|
||||
<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>
|
||||
<el-input-number
|
||||
v-else
|
||||
v-model="row.transportQuantity"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
@change="recalculateAdjustRow(row, 'transportQuantity')"
|
||||
/>
|
||||
<el-table-column label="状态" min-width="120" align="center">
|
||||
<template #default>已生效</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="openAdjustChangeRecord(row)">查看详情</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!adjustChangeRecords.length" description="暂无变更记录" :image-size="60" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="里程(KM)" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.mileage"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
@change="recalculateAdjustRow(row, 'mileage')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运输单价" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.unitPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
@change="recalculateAdjustRow(row, 'unitPrice')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="feeItem in adjustFeeItemNames"
|
||||
:key="feeItem"
|
||||
:label="feeItem"
|
||||
min-width="140"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.feeItems[feeItem]"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
@change="recalculateAdjustRow(row, feeItem)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算金额(含税)" min-width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.settlementAmountTax"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
/>
|
||||
</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"
|
||||
:model="adjustDialog"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="pre-settlement-editor__adjust-reason"
|
||||
>
|
||||
<el-form-item label="调整原因">
|
||||
<el-input
|
||||
v-model="adjustDialog.reason"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="adjustDialog.visible = false">取消</el-button>
|
||||
<el-button
|
||||
@@ -822,35 +853,10 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
<change-record-detail-dialog
|
||||
v-model="adjustChangeRecordVisible"
|
||||
title="变更记录详情"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
width="1100px"
|
||||
top="10px"
|
||||
class="pre-settlement-change-record-detail-dialog"
|
||||
>
|
||||
<div v-if="adjustChangeRecord" class="pre-settlement-change-record-detail-meta">
|
||||
<span>变更日期:{{ adjustChangeRecord.changeTime || '-' }}</span>
|
||||
<span>经办人:{{ adjustChangeRecord.operatorName || '-' }}</span>
|
||||
<span>变更类型:{{ adjustChangeRecord.changeType || '-' }}</span>
|
||||
<span>状态:已生效</span>
|
||||
</div>
|
||||
<el-table :data="adjustChangeRecordDetailRows" border :show-overflow-tooltip="false">
|
||||
<el-table-column prop="field" label="变更字段" min-width="220" />
|
||||
<el-table-column prop="before" label="变更前" min-width="330" />
|
||||
<el-table-column prop="after" label="变更后" min-width="420" />
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-if="!adjustChangeRecordDetailRows.length"
|
||||
description="暂无变更内容"
|
||||
:image-size="60"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="adjustChangeRecordVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
:rows="adjustChangeRecordDetailRows"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="billingRuleDialog.visible"
|
||||
@@ -922,6 +928,8 @@
|
||||
import { h } from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import {
|
||||
adjustDetail,
|
||||
getCandidateDetails,
|
||||
@@ -956,11 +964,12 @@ import {
|
||||
settlementSummaryColumns,
|
||||
} from '@/option/settlement/preSettlementTable';
|
||||
import { downloadFileByUrl } from '@/utils/util';
|
||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
export default {
|
||||
name: 'PreSettlementEditor',
|
||||
components: { InfoFilled },
|
||||
components: { InfoFilled, ChangeRecordDetailDialog },
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
@@ -982,6 +991,10 @@ export default {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
publicMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'success'],
|
||||
data() {
|
||||
@@ -1054,9 +1067,14 @@ export default {
|
||||
batchNo: '',
|
||||
cargoName: '',
|
||||
},
|
||||
detailPage: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
advances: [],
|
||||
changeRecords: [],
|
||||
attachments: [],
|
||||
attachmentTypeOptions: [],
|
||||
selectedAttachmentRows: [],
|
||||
candidateDialog: {
|
||||
visible: false,
|
||||
@@ -1079,7 +1097,6 @@ export default {
|
||||
loading: false,
|
||||
saving: false,
|
||||
readonly: false,
|
||||
activeTab: 'adjust',
|
||||
detailId: '',
|
||||
sourceDetailId: '',
|
||||
detailLineNo: '',
|
||||
@@ -1206,6 +1223,13 @@ export default {
|
||||
})
|
||||
);
|
||||
},
|
||||
pagedDetails() {
|
||||
const list = this.filteredDetails;
|
||||
const size = Number(this.detailPage.size) || 10;
|
||||
const current = Math.max(Number(this.detailPage.current) || 1, 1);
|
||||
const start = (current - 1) * size;
|
||||
return list.slice(start, start + size);
|
||||
},
|
||||
adjustFeeItemNames() {
|
||||
const names = new Set();
|
||||
this.adjustRows.forEach(row => {
|
||||
@@ -1232,6 +1256,16 @@ export default {
|
||||
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
||||
this.recalculateLocalAmount();
|
||||
},
|
||||
filteredDetails: {
|
||||
handler(list) {
|
||||
const size = Number(this.detailPage.size) || 10;
|
||||
const maxPage = Math.max(1, Math.ceil((list?.length || 0) / size) || 1);
|
||||
if (this.detailPage.current > maxPage) {
|
||||
this.detailPage.current = maxPage;
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
@@ -1239,9 +1273,12 @@ export default {
|
||||
},
|
||||
async initialize() {
|
||||
this.resetEditor();
|
||||
await this.loadFeeOptions();
|
||||
await this.loadFeeCategoryOptions();
|
||||
await this.loadTransportTypeOptions();
|
||||
if (!this.publicMode) {
|
||||
await this.loadFeeOptions();
|
||||
await this.loadFeeCategoryOptions();
|
||||
await this.loadTransportTypeOptions();
|
||||
await this.loadAttachmentTypeOptions();
|
||||
}
|
||||
if (this.recordId) await this.loadDetail();
|
||||
else if (this.initialData) {
|
||||
await this.applyInitialData();
|
||||
@@ -1285,6 +1322,7 @@ export default {
|
||||
localCurrency: first.localCurrency || 'RMB',
|
||||
});
|
||||
this.details = rows.map(row => this.normalizeSourceDetail(row));
|
||||
this.detailPage.current = 1;
|
||||
this.loading = true;
|
||||
try {
|
||||
await this.ensureSourceFeeSnapshots();
|
||||
@@ -1346,6 +1384,7 @@ export default {
|
||||
cargoName: '',
|
||||
};
|
||||
this.appliedDetailQuery = { ...this.detailQuery };
|
||||
this.detailPage = { current: 1, size: 10 };
|
||||
this.advances = [];
|
||||
this.changeRecords = [];
|
||||
this.attachments = [];
|
||||
@@ -1354,7 +1393,6 @@ export default {
|
||||
this.pendingAdjustments = {};
|
||||
this.sourceFeeSnapshots = {};
|
||||
this.detailFeeCache = {};
|
||||
this.adjustDialog.activeTab = 'adjust';
|
||||
this.adjustDialog.detailId = '';
|
||||
this.adjustDialog.sourceDetailId = '';
|
||||
this.adjustDialog.detailLineNo = '';
|
||||
@@ -1374,11 +1412,14 @@ export default {
|
||||
async loadDetail() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const { data } = await getDetail(this.form.id || this.recordId);
|
||||
const detail = data?.data || {};
|
||||
const response = this.publicMode
|
||||
? await getMkPublicDetail('pre-settlement', this.form.id || this.recordId)
|
||||
: await getDetail(this.form.id || this.recordId);
|
||||
const detail = response.data?.data || {};
|
||||
this.form = { ...emptyPreSettlementForm(), ...detail };
|
||||
this.summaryFees = (detail.summaryFees || []).map(row => ({ ...row }));
|
||||
this.details = (detail.details || []).map(row => this.normalizeSourceDetail(row));
|
||||
this.detailPage.current = 1;
|
||||
this.advances = (detail.advances || []).map(row => ({
|
||||
...row,
|
||||
createUserName: row.createUserName || detail.createUserName,
|
||||
@@ -1386,6 +1427,7 @@ export default {
|
||||
}));
|
||||
this.changeRecords = detail.changeRecords || [];
|
||||
this.attachments = this.parseAttachments(detail.attachmentsJson);
|
||||
this.sortAttachments();
|
||||
this.selectedAttachmentRows = [];
|
||||
if (
|
||||
detail.contractId &&
|
||||
@@ -1403,8 +1445,15 @@ export default {
|
||||
partyB: detail.payeeName,
|
||||
});
|
||||
}
|
||||
// 预加载所有明细的费用明细数据,用于拼接货物名称和类型
|
||||
await this.loadAllDetailFees();
|
||||
if (this.publicMode && detail.detailFees) {
|
||||
Object.entries(detail.detailFees).forEach(([detailId, feeRows]) => {
|
||||
if (Array.isArray(feeRows) && feeRows.length) {
|
||||
this.detailFeeCache[detailId] = feeRows;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await this.loadAllDetailFees();
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -1563,12 +1612,19 @@ export default {
|
||||
await this.persistPendingAdjustments();
|
||||
if (shouldSubmit) {
|
||||
await submit({ id: this.form.id });
|
||||
await submitMkApprovalFlow({
|
||||
bizType: 'pre-settlement',
|
||||
formInstanceId: this.form.id,
|
||||
subjectName: this.form.preSettlementNo || this.form.contractName || '',
|
||||
approvalStatus: this.form.approvalStatus || '',
|
||||
});
|
||||
this.$message.success('审批流程已发起');
|
||||
this.visible = false;
|
||||
this.$emit('success', this.form.id);
|
||||
return;
|
||||
}
|
||||
this.$message.success('保存成功');
|
||||
this.visible = false;
|
||||
this.$emit('success', this.form.id);
|
||||
} finally {
|
||||
this[stateKey] = false;
|
||||
@@ -1918,6 +1974,7 @@ export default {
|
||||
},
|
||||
handleDetailQuery() {
|
||||
this.appliedDetailQuery = { ...this.detailQuery };
|
||||
this.detailPage.current = 1;
|
||||
},
|
||||
resetDetailQuery() {
|
||||
this.detailQuery = {
|
||||
@@ -1927,6 +1984,13 @@ export default {
|
||||
cargoName: '',
|
||||
};
|
||||
this.appliedDetailQuery = { ...this.detailQuery };
|
||||
this.detailPage.current = 1;
|
||||
},
|
||||
handleDetailPageSizeChange() {
|
||||
this.detailPage.current = 1;
|
||||
},
|
||||
detailIndexMethod(index) {
|
||||
return (this.detailPage.current - 1) * this.detailPage.size + index + 1;
|
||||
},
|
||||
normalizeAdjustRow(item, feeItemNames = []) {
|
||||
const rawFeeItems =
|
||||
@@ -2049,7 +2113,6 @@ export default {
|
||||
this.adjustDialog.visible = true;
|
||||
this.adjustDialog.loading = true;
|
||||
this.adjustDialog.readonly = readonly;
|
||||
this.adjustDialog.activeTab = 'adjust';
|
||||
this.adjustDialog.detailId = detailRow.id;
|
||||
this.adjustDialog.detailLineNo = detailRow.lineNo;
|
||||
this.adjustDialog.sourceDetailId =
|
||||
@@ -2065,6 +2128,11 @@ export default {
|
||||
this.adjustRows = this.cloneAdjustRows(pending.rows);
|
||||
return;
|
||||
}
|
||||
if (this.publicMode) {
|
||||
const cachedFees = this.detailFeeCache[detailRow.id] || [];
|
||||
this.adjustRows = cachedFees.map(item => this.normalizeAdjustRow(item));
|
||||
return;
|
||||
}
|
||||
if (sourceDetailId) {
|
||||
if (!this.sourceFeeSnapshots[sourceDetailId]) {
|
||||
this.sourceFeeSnapshots[sourceDetailId] = await this.loadSourceFeeRows(sourceDetailId);
|
||||
@@ -2072,6 +2140,11 @@ export default {
|
||||
this.adjustRows = this.cloneAdjustRows(this.sourceFeeSnapshots[sourceDetailId]);
|
||||
return;
|
||||
}
|
||||
const cachedFees = this.detailFeeCache[detailRow.id];
|
||||
if (this.publicMode && Array.isArray(cachedFees)) {
|
||||
this.adjustRows = cachedFees.map(item => this.normalizeAdjustRow(item));
|
||||
return;
|
||||
}
|
||||
const response = await getDetailFees(detailRow.id);
|
||||
const rows = response.data?.data || response.data || [];
|
||||
this.adjustRows = rows.map(item => this.normalizeAdjustRow(item));
|
||||
@@ -2260,10 +2333,6 @@ export default {
|
||||
return String(name || '').includes('运费') || String(name || '').includes('运输费');
|
||||
},
|
||||
async saveAdjustment() {
|
||||
if (!this.adjustDialog.reason.trim()) {
|
||||
this.$message.warning('请输入调整原因');
|
||||
return;
|
||||
}
|
||||
if (this.adjustRows.some(row => row.calculating)) {
|
||||
this.$message.warning('费用正在重新计算,请稍候');
|
||||
return;
|
||||
@@ -2442,13 +2511,92 @@ export default {
|
||||
handleAttachmentChange(list) {
|
||||
const uploadUserName = this.userInfo?.realName || this.userInfo?.userName || '';
|
||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
this.attachments = (list || []).map(file => ({
|
||||
...file,
|
||||
uploadUserName: file.uploadUserName || uploadUserName,
|
||||
uploadTime: file.uploadTime || uploadTime,
|
||||
}));
|
||||
const existingRows = this.attachments || [];
|
||||
this.attachments = (list || []).map(file => {
|
||||
const fileUrl = this.attachmentUrl(file);
|
||||
const existing = existingRows.find(item => {
|
||||
const sameUid = file.uid && item.uid && String(file.uid) === String(item.uid);
|
||||
const sameUrl = fileUrl && this.attachmentUrl(item) === fileUrl;
|
||||
return sameUid || sameUrl;
|
||||
});
|
||||
const originalName = this.attachmentName(file);
|
||||
return {
|
||||
...file,
|
||||
type: existing?.type || file.type || this.resolveAttachmentType(originalName),
|
||||
description: existing?.description || file.description || '',
|
||||
uploadUserName: existing?.uploadUserName || file.uploadUserName || uploadUserName,
|
||||
uploadTime: existing?.uploadTime || file.uploadTime || uploadTime,
|
||||
};
|
||||
});
|
||||
this.sortAttachments();
|
||||
this.selectedAttachmentRows = [];
|
||||
},
|
||||
async loadAttachmentTypeOptions() {
|
||||
try {
|
||||
const response = await getDictionary({ code: 'pre_settlement_attachment_type' });
|
||||
const data = response?.data?.data || [];
|
||||
this.attachmentTypeOptions = data
|
||||
.map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictValue,
|
||||
}))
|
||||
.filter(item => item.label && item.value);
|
||||
} catch (error) {
|
||||
this.attachmentTypeOptions = [];
|
||||
}
|
||||
this.sortAttachments();
|
||||
},
|
||||
resolveAttachmentType(fileName) {
|
||||
const normalizedName = String(fileName || '').toLocaleLowerCase();
|
||||
const matchedType = [...this.attachmentTypeOptions]
|
||||
.filter(item => item?.label || item?.value)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
String(b.label || b.value || '').length - String(a.label || a.value || '').length
|
||||
)
|
||||
.find(item => {
|
||||
const typeText = String(item.label || item.value || '').toLocaleLowerCase();
|
||||
return typeText && normalizedName.includes(typeText);
|
||||
});
|
||||
if (matchedType?.value) return matchedType.value;
|
||||
const otherType = this.attachmentTypeOptions.find(item => this.isOtherAttachmentType(item));
|
||||
if (otherType?.value) return otherType.value;
|
||||
return '';
|
||||
},
|
||||
isOtherAttachmentType(item) {
|
||||
const typeText = String(item?.label || item?.value || '').trim();
|
||||
return (
|
||||
typeText === '其他' ||
|
||||
typeText === '其它' ||
|
||||
typeText === '其他附件' ||
|
||||
typeText === '其它附件'
|
||||
);
|
||||
},
|
||||
getAttachmentTypeOrder(type) {
|
||||
const normalizedType = String(type || '').trim();
|
||||
const index = this.attachmentTypeOptions.findIndex(
|
||||
item =>
|
||||
String(item.value || '').trim() === normalizedType ||
|
||||
String(item.label || '').trim() === normalizedType
|
||||
);
|
||||
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
||||
},
|
||||
sortAttachments() {
|
||||
this.attachments = (this.attachments || [])
|
||||
.map((file, index) => ({ file, index }))
|
||||
.sort((a, b) => {
|
||||
const orderDifference =
|
||||
this.getAttachmentTypeOrder(a.file.type) - this.getAttachmentTypeOrder(b.file.type);
|
||||
return orderDifference || a.index - b.index;
|
||||
})
|
||||
.map(item => item.file);
|
||||
},
|
||||
attachmentTypeName(type) {
|
||||
const option = this.attachmentTypeOptions.find(
|
||||
item => String(item.value) === String(type) || String(item.label) === String(type)
|
||||
);
|
||||
return option?.label || type || '-';
|
||||
},
|
||||
handleAttachmentSelectionChange(rows) {
|
||||
this.selectedAttachmentRows = rows || [];
|
||||
},
|
||||
@@ -2492,13 +2640,25 @@ export default {
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value;
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
let list = [];
|
||||
if (Array.isArray(value)) {
|
||||
list = value;
|
||||
} else {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
list = Array.isArray(parsed) ? parsed : [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return list.map(file => {
|
||||
const originalName = this.attachmentName(file);
|
||||
return {
|
||||
...file,
|
||||
type: file?.type || this.resolveAttachmentType(originalName),
|
||||
description: file?.description || '',
|
||||
};
|
||||
});
|
||||
},
|
||||
parseFeeItems(value) {
|
||||
if (!value) return {};
|
||||
@@ -2535,6 +2695,11 @@ export default {
|
||||
if (!Number.isFinite(amount)) return '-';
|
||||
return `${amount.toFixed(2)} ${currency || 'RMB'}`;
|
||||
},
|
||||
formatCreateDate(value) {
|
||||
if (value === undefined || value === null || value === '') return '-';
|
||||
const parsed = this.$dayjs(value);
|
||||
return parsed.isValid() ? parsed.format('YYYY-MM-DD') : this.displayValue(value);
|
||||
},
|
||||
formatQuantity(row) {
|
||||
const value = row.transportQuantity;
|
||||
if (value === undefined || value === null || value === '') return '-';
|
||||
@@ -2685,22 +2850,43 @@ export default {
|
||||
}
|
||||
|
||||
&__detail-filter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 24px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
|
||||
align-items: center;
|
||||
gap: 8px 16px;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px 12px 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
|
||||
:deep(.el-form-item) {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.el-input) {
|
||||
width: 220px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__detail-filter-actions {
|
||||
margin-left: auto;
|
||||
width: auto;
|
||||
margin-left: 0;
|
||||
white-space: nowrap;
|
||||
justify-self: end;
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
flex: none;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
&__contract-filter {
|
||||
@@ -2813,23 +2999,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.pre-settlement-change-record-detail-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 32px;
|
||||
margin-bottom: 16px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
:deep(.pre-settlement-change-record-detail-dialog .el-dialog__body) {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
:deep(.pre-settlement-change-record-detail-dialog .el-table .cell) {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
:deep(.pre-settlement-editor .el-dialog__body) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user