feat: add ERP push failure recovery

This commit is contained in:
zgw
2026-09-15 14:31:16 +08:00
parent 58e9832b61
commit 67251a7562
13 changed files with 851 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
import request from '@/utils/request'
export function listPushExceptions(query) {
return request({
url: '/erp/push-exception/list',
method: 'get',
params: query
})
}
export function retryPushException(data) {
return request({
url: '/erp/push-exception/retry',
method: 'post',
data
})
}

View File

@@ -0,0 +1,224 @@
<template>
<div class="app-container">
<el-form
ref="queryForm"
:model="queryParams"
size="small"
:inline="true"
v-show="showSearch"
label-width="76px"
>
<el-form-item label="单据类型" prop="documentType">
<el-select v-model="queryParams.documentType" placeholder="全部类型" clearable>
<el-option label="入库" value="INBOUND" />
<el-option label="出库" value="OUTBOUND" />
<el-option label="移库" value="MOVE" />
</el-select>
</el-form-item>
<el-form-item label="单据编号" prop="documentNo">
<el-input
v-model="queryParams.documentNo"
placeholder="请输入单据编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="错误信息" prop="errorMessage">
<el-input
v-model="queryParams.errorMessage"
placeholder="请输入错误信息"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-refresh-right"
size="mini"
:disabled="selectedRows.length === 0 || retrying"
v-hasPermi="['erp:pushException:retry']"
@click="handleBatchRetry"
>批量重推</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-alert
class="retry-alert"
title="仅显示最近一个月的失败单据。通信中断时 ERP 可能已经收到单据,重推前请人工确认。"
type="warning"
:closable="false"
show-icon
/>
<el-table v-loading="loading" :data="exceptionList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="48" align="center" />
<el-table-column label="类型" prop="documentType" width="84" align="center">
<template slot-scope="scope">
<el-tag size="small" :type="typeTag(scope.row.documentType)">
{{ typeLabel(scope.row.documentType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="单据编号" prop="documentNo" min-width="170" show-overflow-tooltip />
<el-table-column label="业务信息" prop="businessReference" min-width="210" show-overflow-tooltip />
<el-table-column label="失败时间" prop="failureTime" width="168" />
<el-table-column label="错误原因" prop="errorMessage" min-width="220" show-overflow-tooltip />
<el-table-column label="操作" width="150" fixed="right" align="center">
<template slot-scope="scope">
<el-button
type="text"
size="mini"
icon="el-icon-refresh-right"
:disabled="retrying"
:loading="retryingId === rowKey(scope.row)"
v-hasPermi="['erp:pushException:retry']"
@click="handleRetry(scope.row)"
>重推</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { listPushExceptions, retryPushException } from '@/api/erp/pushException'
export default {
name: 'ErpPushException',
data() {
return {
loading: false,
retrying: false,
retryingId: null,
showSearch: true,
total: 0,
exceptionList: [],
selectedRows: [],
queryParams: {
pageNum: 1,
pageSize: 10,
documentType: null,
documentNo: null,
errorMessage: null
}
}
},
created() {
this.getList()
},
methods: {
getList() {
this.loading = true
return listPushExceptions(this.queryParams).then(response => {
this.exceptionList = response.rows || []
this.total = response.total || 0
}).finally(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
handleSelectionChange(selection) {
this.selectedRows = selection
},
handleRetry(row) {
this.confirmRetry(1).then(() => {
this.retrying = true
this.retryingId = this.rowKey(row)
return retryPushException(this.retryPayload(row))
}).then(() => {
this.$modal.msgSuccess('已加入重试队列')
return this.getList()
}).catch(() => {
if (this.retryingId) {
return this.getList().catch(() => {})
}
}).finally(() => {
this.retrying = false
this.retryingId = null
})
},
async handleBatchRetry() {
const rows = this.selectedRows.slice()
try {
await this.confirmRetry(rows.length)
} catch (error) {
return
}
this.retrying = true
let succeeded = 0
const failed = []
try {
for (const row of rows) {
try {
await retryPushException(this.retryPayload(row))
succeeded += 1
} catch (error) {
failed.push(row.documentNo)
}
}
} finally {
this.retrying = false
await this.getList().catch(() => {})
}
if (failed.length === 0) {
this.$modal.msgSuccess(`已将 ${succeeded} 条单据加入重试队列`)
} else {
this.$modal.msgWarning(`成功 ${succeeded} 条,失败 ${failed.length} 条:${failed.join('、')}`)
}
},
confirmRetry(count) {
return this.$modal.confirm(
`确定重新推送 ${count} 条单据吗?如果 ERP 已收到但响应丢失,重复结果由 ERP 的单号校验规则决定。`
)
},
retryPayload(row) {
return {
documentType: row.documentType,
recordId: row.recordId
}
},
rowKey(row) {
return `${row.documentType}:${row.recordId}`
},
typeLabel(type) {
return { INBOUND: '入库', OUTBOUND: '出库', MOVE: '移库' }[type] || type
},
typeTag(type) {
return { INBOUND: 'success', OUTBOUND: 'danger', MOVE: 'warning' }[type] || 'info'
}
}
}
</script>
<style scoped>
.retry-alert {
margin-bottom: 12px;
}
</style>