1、新增结算模块
2、调整业务模块 3、调整客商模块
This commit is contained in:
@@ -0,0 +1,832 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="98%" top="2vh" append-to-body destroy-on-close>
|
||||
<div v-loading="loading" class="reconciliation-editor">
|
||||
<section class="reconciliation-editor__section">
|
||||
<div class="dialog-section-title">导入外部账单,与内部账单核对</div>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col v-for="field in formFields" :key="field.prop" :span="8">
|
||||
<el-form-item :label="field.label" :prop="field.prop">
|
||||
<el-input
|
||||
v-if="field.prop === 'formalSettlementNo' && editable"
|
||||
v-model="form.formalSettlementNo"
|
||||
readonly
|
||||
placeholder="请选择正式结算单"
|
||||
@click="openFormalDialog"
|
||||
>
|
||||
<template #append><el-button @click="openFormalDialog">选择</el-button></template>
|
||||
</el-input>
|
||||
<el-select
|
||||
v-else-if="field.prop === 'reconciliationMode' && editable"
|
||||
v-model="form.reconciliationMode"
|
||||
>
|
||||
<el-option label="整车总额对账" value="vehicle" />
|
||||
<el-option label="货物明细对账" value="cargo" />
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-else-if="field.type === 'date' && editable"
|
||||
v-model="form.reconciliationDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
<el-input-number
|
||||
v-else-if="field.money"
|
||||
v-model="form[field.prop]"
|
||||
:controls="false"
|
||||
:precision="2"
|
||||
disabled
|
||||
/>
|
||||
<span v-else>{{ displayValue(form[field.prop]) }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-if="editable"
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
<span v-else>{{ displayValue(form.remark) }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</section>
|
||||
|
||||
<section class="reconciliation-editor__section">
|
||||
<div class="dialog-section-title">内部账单</div>
|
||||
<div class="reconciliation-editor__stats">
|
||||
<div class="stat-card">
|
||||
<b>{{ form.internalBillCount || 0 }}</b
|
||||
><span>我方账单数</span
|
||||
><small
|
||||
>对方 {{ form.externalBillCount || 0 }} 条 / 差异
|
||||
{{ form.differenceCount || 0 }} 条</small
|
||||
>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<b>{{ formatNumber(form.internalQuantity) }}</b
|
||||
><span>我方货量</span
|
||||
><small
|
||||
>对方 {{ formatNumber(form.externalQuantity) }} / 差异
|
||||
{{ formatNumber(form.differenceQuantity) }}</small
|
||||
>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<b>{{ formatMoney(form.internalAmount) }}</b
|
||||
><span>我方结算金额</span
|
||||
><small
|
||||
>对方 {{ formatMoney(form.externalAmount) }} / 差异
|
||||
{{ formatMoney(form.differenceAmount) }}</small
|
||||
>
|
||||
</div>
|
||||
<div class="stat-card stat-card--success">
|
||||
<b>{{ form.matchedCount || 0 }}</b
|
||||
><span>成功匹配(条)</span>
|
||||
</div>
|
||||
<div class="stat-card stat-card--danger">
|
||||
<b>{{ form.unmatchedCount || 0 }}</b
|
||||
><span>无法匹配(条)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reconciliation-editor__filter">
|
||||
<el-input v-model="internalQuery.documentNo" placeholder="单据号" clearable />
|
||||
<el-input v-model="internalQuery.vehicleNo" placeholder="车号" clearable />
|
||||
<el-input v-model="internalQuery.batchNo" placeholder="批次号" clearable />
|
||||
<el-input v-model="internalQuery.cargoName" placeholder="货物名称" clearable />
|
||||
<el-button @click="resetInternalQuery">重置</el-button>
|
||||
<el-button type="primary" @click="internalFilterTick++">查询</el-button>
|
||||
</div>
|
||||
<el-table :data="filteredInternalRows" border max-height="420">
|
||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in internalColumns"
|
||||
:key="column.prop"
|
||||
v-bind="column"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="column.prop === 'matchResult'" :type="matchTagType(row.matchResult)">{{
|
||||
matchName(row.matchResult)
|
||||
}}</el-tag>
|
||||
<el-tag
|
||||
v-else-if="column.prop === 'updateResult'"
|
||||
:type="updateTagType(row.updateResult)"
|
||||
>{{ updateName(row.updateResult) }}</el-tag
|
||||
>
|
||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="reconciliation-editor__links">
|
||||
<el-link v-if="editable" type="primary" @click="openAdjust(row)">调整</el-link>
|
||||
<el-link
|
||||
v-if="editable && row.matchResult === 'matched'"
|
||||
type="primary"
|
||||
@click="handleUnmatch(row)"
|
||||
>取消匹配</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="editable && row.matchResult !== 'matched'"
|
||||
type="primary"
|
||||
@click="openManualMatch(row)"
|
||||
>人工匹配</el-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="reconciliation-editor__section">
|
||||
<div class="reconciliation-editor__section-head">
|
||||
<div class="dialog-section-title">导入外部账单</div>
|
||||
<div class="reconciliation-editor__actions" v-if="editable">
|
||||
<el-button type="primary" plain @click="downloadTemplate('vehicle')"
|
||||
>下载整车对账模板</el-button
|
||||
>
|
||||
<el-button type="primary" plain @click="downloadTemplate('cargo')"
|
||||
>下载货物明细对账模板</el-button
|
||||
>
|
||||
<el-button type="primary" plain @click="chooseImport">导入</el-button>
|
||||
<el-button type="primary" @click="handleMatch">开始匹配内部账单</el-button>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept=".xls,.xlsx"
|
||||
class="hidden-file"
|
||||
@change="handleImport"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-tabs v-model="externalTab">
|
||||
<el-tab-pane :label="`导入明细(${externalDetails.length})`" name="all" />
|
||||
<el-tab-pane :label="`疑似重复(${duplicateRows.length})`" name="duplicate" />
|
||||
</el-tabs>
|
||||
<el-table :data="visibleExternalRows" border max-height="420">
|
||||
<el-table-column
|
||||
v-for="column in externalColumns"
|
||||
:key="column.prop"
|
||||
v-bind="column"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="column.prop === 'matchStatus'" :type="matchTagType(row.matchStatus)">{{
|
||||
matchName(row.matchStatus)
|
||||
}}</el-tag>
|
||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" fixed="right" align="center">
|
||||
<template #default="{ row }"
|
||||
><el-link
|
||||
v-if="editable && row.matchStatus !== 'matched'"
|
||||
type="primary"
|
||||
@click="openManualMatchByExternal(row)"
|
||||
>匹配</el-link
|
||||
></template
|
||||
>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="reconciliation-editor__section">
|
||||
<el-collapse>
|
||||
<el-collapse-item title="操作说明" name="help">
|
||||
<div class="reconciliation-editor__help">
|
||||
<p>1. 新增对账单时选择一张已审批通过的正式结算单,系统自动带出内部账单明细。</p>
|
||||
<p>
|
||||
2. 导入对方 Excel
|
||||
账单后点击“开始匹配内部账单”,系统按车号、货物、地址、批次、发货时间、运输量和金额匹配。
|
||||
</p>
|
||||
<p>3. 外部账单存在重复候选时会标记为疑似重复,需要人工选择唯一明细匹配。</p>
|
||||
<p>4. 只有所有内外部明细一一匹配且差异为 0,才允许按匹配结果更新账单或完成对账。</p>
|
||||
<p>
|
||||
5.
|
||||
已付金额大于外部匹配金额时禁止更新;整车模式一车多货会跳过自动更新,可通过“调整”逐货物修改。
|
||||
</p>
|
||||
<p>6. 按匹配结果更新成功后生成变更记录;完成对账后单据不可再编辑和删除。</p>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<template v-if="editable">
|
||||
<el-button type="primary" plain :loading="saving" @click="handleSave">保存草稿</el-button>
|
||||
<el-button type="primary" plain :loading="actionLoading" @click="handleUpdate"
|
||||
>按匹配结果更新账单</el-button
|
||||
>
|
||||
<el-button type="primary" :loading="actionLoading" @click="handleComplete"
|
||||
>完成对账</el-button
|
||||
>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<el-dialog v-model="formalDialog.visible" title="选择正式结算单" width="84%" append-to-body>
|
||||
<el-form :model="formalDialog.query" inline label-position="right" label-width="160px">
|
||||
<el-form-item label="正式结算单号"
|
||||
><el-input v-model="formalDialog.query.keyword" clearable
|
||||
/></el-form-item>
|
||||
<el-form-item><el-button @click="loadFormalOptions">查询</el-button></el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
v-loading="formalDialog.loading"
|
||||
:data="formalDialog.rows"
|
||||
border
|
||||
@selection-change="formalDialog.selected = $event"
|
||||
>
|
||||
<el-table-column type="selection" width="52" :selectable="row => !row.isSelected" />
|
||||
<el-table-column type="index" label="序号" width="64" />
|
||||
<el-table-column prop="formalSettlementNo" label="正式结算单号" min-width="150" />
|
||||
<el-table-column prop="contractNo" label="合同编号" min-width="130" />
|
||||
<el-table-column prop="contractName" label="合同名称" min-width="160" />
|
||||
<el-table-column prop="payerName" label="付款方" min-width="150" />
|
||||
<el-table-column prop="payeeName" label="收款方" min-width="150" />
|
||||
<el-table-column prop="settlementAmount" label="结算金额" min-width="120"
|
||||
><template #default="{ row }">{{
|
||||
formatMoney(row.settlementAmount, row.currency)
|
||||
}}</template></el-table-column
|
||||
>
|
||||
</el-table>
|
||||
<div class="reconciliation-editor__pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="formalDialog.page.current"
|
||||
v-model:page-size="formalDialog.page.size"
|
||||
:total="formalDialog.page.total"
|
||||
layout="total, prev, pager, next"
|
||||
@current-change="loadFormalOptions"
|
||||
/>
|
||||
</div>
|
||||
<template #footer
|
||||
><el-button @click="formalDialog.visible = false">取消</el-button
|
||||
><el-button type="primary" @click="confirmFormal">确定</el-button></template
|
||||
>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="adjustDialog.visible" title="结算明细调整" width="90%" append-to-body>
|
||||
<el-table :data="adjustDialog.rows" border>
|
||||
<el-table-column prop="cargoName" label="货物名称" min-width="140" />
|
||||
<el-table-column prop="cargoType" label="货物类型" min-width="120" />
|
||||
<el-table-column prop="transportQuantity" label="运输量" min-width="120"
|
||||
><template #default="{ row }"
|
||||
><el-input-number
|
||||
v-model="row.transportQuantity"
|
||||
:min="0"
|
||||
:controls="false" /></template
|
||||
></el-table-column>
|
||||
<el-table-column prop="unitPrice" label="运输单价" min-width="120"
|
||||
><template #default="{ row }"
|
||||
><el-input-number v-model="row.unitPrice" :min="0" :controls="false" /></template
|
||||
></el-table-column>
|
||||
<el-table-column prop="freightAmount" label="运输费" min-width="120"
|
||||
><template #default="{ row }"
|
||||
><el-input-number v-model="row.freightAmount" :min="0" :controls="false" /></template
|
||||
></el-table-column>
|
||||
<el-table-column prop="settlementAmount" label="结算金额" min-width="140"
|
||||
><template #default="{ row }"
|
||||
><el-input-number v-model="row.settlementAmount" :min="0" :controls="false" /></template
|
||||
></el-table-column>
|
||||
</el-table>
|
||||
<el-form class="dialog-form" label-position="right" label-width="auto"
|
||||
><el-form-item label="调整原因" required
|
||||
><el-input
|
||||
v-model="adjustDialog.reason"
|
||||
type="textarea"
|
||||
maxlength="200"
|
||||
show-word-limit /></el-form-item
|
||||
></el-form>
|
||||
<template #footer
|
||||
><el-button @click="adjustDialog.visible = false">取消</el-button
|
||||
><el-button type="primary" :loading="adjustDialog.saving" @click="saveAdjust"
|
||||
>保存</el-button
|
||||
></template
|
||||
>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="manualDialog.visible" title="选择外部账单明细" width="86%" append-to-body>
|
||||
<el-table :data="unmatchedExternalRows" border @row-click="manualDialog.selected = $event">
|
||||
<el-table-column type="index" label="序号" width="64" />
|
||||
<el-table-column prop="externalLineNo" label="外部行号" width="90" />
|
||||
<el-table-column prop="vehicleNo" label="车牌号" width="110" />
|
||||
<el-table-column prop="cargoName" label="货物名称" min-width="140" />
|
||||
<el-table-column prop="transportQuantity" label="运输量" width="110" />
|
||||
<el-table-column prop="settlementAmount" label="结算金额" width="130"
|
||||
><template #default="{ row }">{{
|
||||
formatMoney(row.settlementAmount)
|
||||
}}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="选择" width="80"
|
||||
><template #default="{ row }"
|
||||
><el-radio v-model="manualDialog.selected" :label="row"> </el-radio></template
|
||||
></el-table-column
|
||||
>
|
||||
</el-table>
|
||||
<template #footer
|
||||
><el-button @click="manualDialog.visible = false">取消</el-button
|
||||
><el-button type="primary" @click="confirmManualMatch">确定</el-button></template
|
||||
>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as api from '@/api/settlement/transportReconciliation';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { transportReconciliationFormFields } from '@/option/settlement/transportReconciliationForm';
|
||||
import {
|
||||
internalColumns,
|
||||
externalCargoColumns,
|
||||
externalVehicleColumns,
|
||||
} from '@/option/settlement/transportReconciliationTable';
|
||||
|
||||
export default {
|
||||
name: 'TransportReconciliationEditor',
|
||||
props: {
|
||||
modelValue: Boolean,
|
||||
recordId: [String, Number],
|
||||
settlementType: { type: String, default: 'payable' },
|
||||
readonly: Boolean,
|
||||
},
|
||||
emits: ['update:modelValue', 'success'],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
saving: false,
|
||||
actionLoading: false,
|
||||
currentId: null,
|
||||
form: this.emptyForm(),
|
||||
formFields: transportReconciliationFormFields,
|
||||
internalColumns,
|
||||
internalDetails: [],
|
||||
externalDetails: [],
|
||||
internalQuery: { documentNo: '', vehicleNo: '', batchNo: '', cargoName: '' },
|
||||
internalFilterTick: 0,
|
||||
externalTab: 'all',
|
||||
formalDialog: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
rows: [],
|
||||
selected: [],
|
||||
query: { keyword: '' },
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
},
|
||||
adjustDialog: { visible: false, saving: false, rows: [], reason: '' },
|
||||
manualDialog: { visible: false, internal: null, selected: null },
|
||||
rules: {
|
||||
formalSettlementNo: [{ required: true, message: '请选择正式结算单', trigger: 'change' }],
|
||||
reconciliationMode: [{ required: true, message: '请选择对账模式', trigger: 'change' }],
|
||||
remark: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
visible: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:modelValue', value);
|
||||
},
|
||||
},
|
||||
editable() {
|
||||
return !this.readonly;
|
||||
},
|
||||
title() {
|
||||
return this.readonly
|
||||
? '查看运输对账单'
|
||||
: this.currentId
|
||||
? '编辑运输对账单'
|
||||
: '新增运输对账单';
|
||||
},
|
||||
filteredInternalRows() {
|
||||
void this.internalFilterTick;
|
||||
const query = this.internalQuery;
|
||||
return this.internalDetails.filter(
|
||||
row =>
|
||||
(!query.documentNo || String(row.documentNo || '').includes(query.documentNo)) &&
|
||||
(!query.vehicleNo || String(row.vehicleNo || '').includes(query.vehicleNo)) &&
|
||||
(!query.batchNo || String(row.batchNo || '').includes(query.batchNo)) &&
|
||||
(!query.cargoName || String(row.cargoName || '').includes(query.cargoName))
|
||||
);
|
||||
},
|
||||
externalColumns() {
|
||||
return this.form.reconciliationMode === 'cargo'
|
||||
? externalCargoColumns
|
||||
: externalVehicleColumns;
|
||||
},
|
||||
duplicateRows() {
|
||||
return this.externalDetails.filter(
|
||||
row => row.suspectedDuplicate || row.matchStatus === 'suspected_duplicate'
|
||||
);
|
||||
},
|
||||
visibleExternalRows() {
|
||||
return this.externalTab === 'duplicate' ? this.duplicateRows : this.externalDetails;
|
||||
},
|
||||
unmatchedExternalRows() {
|
||||
return this.externalDetails.filter(
|
||||
row => row.matchStatus !== 'matched' && !row.suspectedDuplicate
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(value) {
|
||||
if (value) this.initialize();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
emptyForm() {
|
||||
return {
|
||||
reconciliationNo: '',
|
||||
reconciliationMode: 'vehicle',
|
||||
formalSettlementId: null,
|
||||
formalSettlementNo: '',
|
||||
payerName: '',
|
||||
payeeName: '',
|
||||
projectName: '',
|
||||
deptName: '',
|
||||
contractName: '',
|
||||
paidAmount: 0,
|
||||
reconcilerName: '',
|
||||
reconciliationDate: '',
|
||||
remark: '',
|
||||
internalBillCount: 0,
|
||||
externalBillCount: 0,
|
||||
differenceCount: 0,
|
||||
internalQuantity: 0,
|
||||
externalQuantity: 0,
|
||||
differenceQuantity: 0,
|
||||
internalAmount: 0,
|
||||
externalAmount: 0,
|
||||
differenceAmount: 0,
|
||||
matchedCount: 0,
|
||||
unmatchedCount: 0,
|
||||
};
|
||||
},
|
||||
async initialize() {
|
||||
this.currentId = this.recordId;
|
||||
this.form = this.emptyForm();
|
||||
this.internalDetails = [];
|
||||
this.externalDetails = [];
|
||||
this.externalTab = 'all';
|
||||
this.internalQuery = { documentNo: '', vehicleNo: '', batchNo: '', cargoName: '' };
|
||||
if (!this.currentId) {
|
||||
this.form.reconciliationMode = 'vehicle';
|
||||
this.form.reconcilerName = '当前用户';
|
||||
this.form.reconciliationDate = this.$dayjs().format('YYYY-MM-DD');
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
await this.loadDetail();
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async loadDetail() {
|
||||
const { data } = await api.getDetail(this.currentId);
|
||||
this.form = { ...this.emptyForm(), ...data };
|
||||
this.internalDetails = data.internalDetails || [];
|
||||
this.externalDetails = data.externalDetails || [];
|
||||
},
|
||||
openFormalDialog() {
|
||||
if (!this.editable) return;
|
||||
this.formalDialog.visible = true;
|
||||
this.formalDialog.page.current = 1;
|
||||
this.loadFormalOptions();
|
||||
},
|
||||
async loadFormalOptions() {
|
||||
this.formalDialog.loading = true;
|
||||
try {
|
||||
const { data } = await api.getFormalOptions(
|
||||
this.formalDialog.page.current,
|
||||
this.formalDialog.page.size,
|
||||
{ settlementType: this.settlementType, keyword: this.formalDialog.query.keyword }
|
||||
);
|
||||
this.formalDialog.rows = data.records || [];
|
||||
this.formalDialog.page.total = data.total || 0;
|
||||
} finally {
|
||||
this.formalDialog.loading = false;
|
||||
}
|
||||
},
|
||||
confirmFormal() {
|
||||
if (this.formalDialog.selected.length !== 1)
|
||||
return this.$message.warning('请选择一张正式结算单');
|
||||
const selected = this.formalDialog.selected[0];
|
||||
this.form = {
|
||||
...this.form,
|
||||
...selected,
|
||||
formalSettlementId: selected.id,
|
||||
formalSettlementNo: selected.formalSettlementNo,
|
||||
reconciliationNo: this.form.reconciliationNo,
|
||||
};
|
||||
this.formalDialog.visible = false;
|
||||
},
|
||||
async handleSave() {
|
||||
const valid = await this.$refs.formRef.validate().catch(() => false);
|
||||
if (!valid || !this.form.formalSettlementId) return this.$message.warning('请选择正式结算单');
|
||||
this.saving = true;
|
||||
try {
|
||||
const { data } = await api.save({
|
||||
id: this.currentId,
|
||||
formalSettlementId: this.form.formalSettlementId,
|
||||
reconciliationMode: this.form.reconciliationMode,
|
||||
reconciliationDate: this.form.reconciliationDate,
|
||||
remark: this.form.remark,
|
||||
});
|
||||
this.currentId = data;
|
||||
await this.loadDetail();
|
||||
this.$message.success('草稿保存成功');
|
||||
this.$emit('success');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
async handleMatch() {
|
||||
if (!this.currentId) {
|
||||
await this.handleSave();
|
||||
if (!this.currentId) return;
|
||||
}
|
||||
this.actionLoading = true;
|
||||
try {
|
||||
await api.match(this.currentId);
|
||||
await this.loadDetail();
|
||||
this.$message.success('匹配完成');
|
||||
} finally {
|
||||
this.actionLoading = false;
|
||||
}
|
||||
},
|
||||
async handleUpdate() {
|
||||
if (!this.currentId) return this.$message.warning('请先保存对账单');
|
||||
await this.$confirm('将以外部账单匹配金额更新内部结算明细,是否继续?', '更新账单', {
|
||||
type: 'warning',
|
||||
});
|
||||
this.actionLoading = true;
|
||||
try {
|
||||
await api.updateByMatch(this.currentId);
|
||||
await this.loadDetail();
|
||||
this.$message.success('账单更新完成');
|
||||
} finally {
|
||||
this.actionLoading = false;
|
||||
}
|
||||
},
|
||||
async handleComplete() {
|
||||
if (!this.currentId) return this.$message.warning('请先保存对账单');
|
||||
await this.$confirm('完成后对账单不可修改和删除,是否继续?', '完成对账', {
|
||||
type: 'warning',
|
||||
});
|
||||
this.actionLoading = true;
|
||||
try {
|
||||
await api.complete(this.currentId);
|
||||
await this.loadDetail();
|
||||
this.$message.success('对账完成');
|
||||
this.$emit('success');
|
||||
} finally {
|
||||
this.actionLoading = false;
|
||||
}
|
||||
},
|
||||
chooseImport() {
|
||||
if (!this.currentId) return this.$message.warning('请先保存对账单');
|
||||
this.$refs.fileInput?.click();
|
||||
},
|
||||
async handleImport(event) {
|
||||
const file = event.target.files?.[0];
|
||||
event.target.value = '';
|
||||
if (!file) return;
|
||||
try {
|
||||
const response =
|
||||
this.form.reconciliationMode === 'cargo'
|
||||
? await api.importCargo(this.currentId, file)
|
||||
: await api.importVehicle(this.currentId, file);
|
||||
const contentType = response.headers?.['content-type'] || response.data?.type || '';
|
||||
if (contentType.includes('spreadsheetml') || contentType.includes('ms-excel')) {
|
||||
downloadXls(
|
||||
response.data,
|
||||
`运输对账导入失败明细${this.$dayjs().format('YYYY-MM-DD HH-mm-ss')}.xlsx`
|
||||
);
|
||||
this.$message.warning('部分数据导入失败,已下载失败明细');
|
||||
} else {
|
||||
const result = JSON.parse(await response.data.text());
|
||||
if (result.code !== 200) throw new Error(result.msg || '导入失败');
|
||||
this.$message.success('外部账单导入成功');
|
||||
}
|
||||
await this.loadDetail();
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '外部账单导入失败');
|
||||
}
|
||||
},
|
||||
async downloadTemplate(mode) {
|
||||
const response = await api.template(mode);
|
||||
downloadXls(
|
||||
response.data,
|
||||
`${mode === 'cargo' ? '货物明细对账模板' : '整车总额对账模板'}.xlsx`
|
||||
);
|
||||
},
|
||||
resetInternalQuery() {
|
||||
this.internalQuery = { documentNo: '', vehicleNo: '', batchNo: '', cargoName: '' };
|
||||
this.internalFilterTick++;
|
||||
},
|
||||
openAdjust(row) {
|
||||
this.adjustDialog = {
|
||||
visible: true,
|
||||
saving: false,
|
||||
reason: '',
|
||||
rows: this.internalDetails
|
||||
.filter(item => item.formalSettlementDetailId === row.formalSettlementDetailId)
|
||||
.map(item => ({ ...item })),
|
||||
};
|
||||
},
|
||||
async saveAdjust() {
|
||||
if (!this.adjustDialog.reason.trim()) return this.$message.warning('请输入调整原因');
|
||||
this.adjustDialog.saving = true;
|
||||
try {
|
||||
for (const row of this.adjustDialog.rows)
|
||||
await api.adjust({
|
||||
...row,
|
||||
reconciliationId: this.currentId,
|
||||
updateMessage: this.adjustDialog.reason,
|
||||
});
|
||||
this.adjustDialog.visible = false;
|
||||
await this.loadDetail();
|
||||
this.$message.success('调整保存成功');
|
||||
} finally {
|
||||
this.adjustDialog.saving = false;
|
||||
}
|
||||
},
|
||||
async handleUnmatch(row) {
|
||||
await api.unmatch(row.id);
|
||||
await this.loadDetail();
|
||||
},
|
||||
openManualMatch(row) {
|
||||
this.manualDialog = { visible: true, internal: row, selected: null };
|
||||
},
|
||||
openManualMatchByExternal(row) {
|
||||
this.manualDialog = { visible: true, internal: null, selected: row };
|
||||
},
|
||||
async confirmManualMatch() {
|
||||
if (!this.manualDialog.selected) return this.$message.warning('请选择外部账单明细');
|
||||
if (!this.manualDialog.internal) return this.$message.warning('请从内部账单行发起人工匹配');
|
||||
await api.manualMatch({
|
||||
reconciliationId: this.currentId,
|
||||
internalId: this.manualDialog.internal.id,
|
||||
externalId: this.manualDialog.selected.id,
|
||||
});
|
||||
this.manualDialog.visible = false;
|
||||
await this.loadDetail();
|
||||
this.$message.success('人工匹配成功');
|
||||
},
|
||||
matchName(value) {
|
||||
return (
|
||||
{
|
||||
matched: '已匹配',
|
||||
suspected_duplicate: '疑似重复',
|
||||
partial: '部分匹配',
|
||||
unmatched: '未匹配',
|
||||
}[value] || '未匹配'
|
||||
);
|
||||
},
|
||||
matchTagType(value) {
|
||||
return value === 'matched'
|
||||
? 'success'
|
||||
: value === 'suspected_duplicate'
|
||||
? 'danger'
|
||||
: value === 'partial'
|
||||
? 'warning'
|
||||
: 'info';
|
||||
},
|
||||
updateName(value) {
|
||||
return (
|
||||
{
|
||||
updated: '已更新',
|
||||
skipped_multi_cargo: '跳过更新',
|
||||
manually_adjusted: '手工调整',
|
||||
not_updated: '未更新',
|
||||
}[value] ||
|
||||
value ||
|
||||
'未更新'
|
||||
);
|
||||
},
|
||||
updateTagType(value) {
|
||||
return value === 'updated' || value === 'manually_adjusted'
|
||||
? 'success'
|
||||
: value === 'skipped_multi_cargo'
|
||||
? 'warning'
|
||||
: 'info';
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : value;
|
||||
},
|
||||
formatMoney(value, currency = 'RMB') {
|
||||
return `${Number(value || 0).toFixed(2)} ${currency || 'RMB'}`;
|
||||
},
|
||||
formatNumber(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.reconciliation-editor__section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.reconciliation-editor__section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.reconciliation-editor__actions,
|
||||
.reconciliation-editor__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.reconciliation-editor__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.stat-card {
|
||||
min-height: 104px;
|
||||
border: 1px solid #eff1f7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
background: #fff;
|
||||
}
|
||||
.stat-card b {
|
||||
font-size: 20px;
|
||||
color: #303133;
|
||||
}
|
||||
.stat-card span {
|
||||
color: #606266;
|
||||
}
|
||||
.stat-card small {
|
||||
color: #909399;
|
||||
}
|
||||
.stat-card--success b {
|
||||
color: #67c23a;
|
||||
}
|
||||
.stat-card--danger b {
|
||||
color: #f56c6c;
|
||||
}
|
||||
.reconciliation-editor__filter {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(160px, 1fr)) auto auto;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.reconciliation-editor__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.reconciliation-editor__help {
|
||||
color: #606266;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.dialog-form {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.hidden-file {
|
||||
display: none;
|
||||
}
|
||||
.reconciliation-editor :deep(.el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.reconciliation-editor :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.reconciliation-editor :deep(.el-table__body tr:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.reconciliation-editor__stats {
|
||||
grid-template-columns: repeat(3, minmax(150px, 1fr));
|
||||
}
|
||||
.reconciliation-editor__filter {
|
||||
grid-template-columns: repeat(2, minmax(160px, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user