清理失效支付,会议纪要展示去重

master
weizong song 3 weeks ago
parent 5dc8db772b
commit 3ae371f458

@ -268,8 +268,8 @@
{{ idx + 1 }}{{ template.flow_info?.no || `流程ID: ${template.flow_id}` }} - {{ template.flow_info?.title || template.flow_title || '-' }}{{ template.flow_info?.creator_name || '-' }} {{ formatFlowCreatedAt(template.flow_info?.created_at) }} {{ idx + 1 }}{{ template.flow_info?.no || `流程ID: ${template.flow_id}` }} - {{ template.flow_info?.title || template.flow_title || '-' }}{{ template.flow_info?.creator_name || '-' }} {{ formatFlowCreatedAt(template.flow_info?.created_at) }}
</h3> </h3>
<div class="flow-template-content" v-html="template.html"></div> <div class="flow-template-content" v-html="template.html"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -376,12 +376,10 @@ const collectedFlowIds = ref(new Set())
const renderedPrintTemplates = ref([]) // HTML const renderedPrintTemplates = ref([]) // HTML
const REPEATED_MEETING_MINUTES_MESSAGE = '会议纪要再次使用,仅展示标题,不重复打印' const REPEATED_MEETING_MINUTES_MESSAGE = '会议纪要再次使用,仅展示标题,不重复打印'
let meetingMinutesUsageStack = []
let meetingMinutesUsageCache = {} let meetingMinutesUsageCache = {}
let meetingMinutesSeenMap = new Map() let meetingMinutesSeenMap = new Map()
const resetMeetingMinutesUsage = () => { const resetMeetingMinutesUsage = () => {
meetingMinutesUsageStack = []
meetingMinutesUsageCache = {} meetingMinutesUsageCache = {}
meetingMinutesSeenMap = new Map() meetingMinutesSeenMap = new Map()
} }
@ -411,21 +409,38 @@ const getMeetingMinutesDisplayTitle = (rawVal) => {
return '会议纪要' return '会议纪要'
} }
const extractMeetingMinuteId = (rawVal) => { const extractMeetingMinutesContentHash = (rawVal) => {
const parsed = parseMeetingMinutesValue(rawVal) const parsed = parseMeetingMinutesValue(rawVal)
if (!parsed || typeof parsed !== 'object') return null if (!parsed || typeof parsed !== 'object') return null
const id = parsed.meeting_minute_id
if (id === null || id === undefined || id === '') return null const candidates = [
const normalized = Number(id) parsed.content_hash,
if (!Number.isFinite(normalized) || normalized <= 0) return null parsed.contentHash
return normalized ]
for (const candidate of candidates) {
if (candidate === null || candidate === undefined) continue
const normalized = String(candidate).trim()
if (normalized) {
return normalized
}
}
return null
} }
const buildMeetingMinutesFingerprint = (rawVal) => { const buildMeetingMinutesFingerprint = (rawVal) => {
const meetingMinuteId = extractMeetingMinuteId(rawVal) const contentHash = extractMeetingMinutesContentHash(rawVal)
if (meetingMinuteId) { if (contentHash) {
return `meeting_minute_id:${meetingMinuteId}` return `meeting_minutes_hash:${contentHash}`
} }
const parsed = parseMeetingMinutesValue(rawVal)
if (!parsed || typeof parsed !== 'object') return null
const fallbackMeetingMinuteId = parsed.meeting_minute_id || parsed.original_meeting_minute_id || parsed.source_meeting_minute_id
if (fallbackMeetingMinuteId) {
return `meeting_minutes_id:${fallbackMeetingMinuteId}`
}
return null return null
} }
@ -456,12 +471,6 @@ const resolveMeetingMinutesUsage = (occurrenceKey, rawVal) => {
meetingMinutesSeenMap.set(fingerprint, occurrenceKey) meetingMinutesSeenMap.set(fingerprint, occurrenceKey)
} }
meetingMinutesUsageStack.push({
occurrenceKey,
fingerprint,
title: state.title,
isDuplicate: state.isDuplicate
})
meetingMinutesUsageCache[occurrenceKey] = state meetingMinutesUsageCache[occurrenceKey] = state
return state return state
} }
@ -1518,6 +1527,37 @@ onMounted(async () => {
font-weight: 600; font-weight: 600;
} }
.meeting-minutes-debug {
margin-top: 24px;
}
.debug-block {
margin-top: 12px;
border: 1px solid #d1d5db;
border-radius: 8px;
background: #f9fafb;
overflow: hidden;
}
.debug-title {
padding: 10px 12px;
font-size: 13px;
font-weight: 700;
color: #111827;
border-bottom: 1px solid #e5e7eb;
background: #f3f4f6;
}
.debug-pre {
margin: 0;
padding: 12px;
white-space: pre-wrap;
word-break: break-all;
font-size: 12px;
line-height: 1.6;
color: #1f2937;
}
.breadcrumb { .breadcrumb {
color: #909399; color: #909399;
margin-left: 8px; margin-left: 8px;

@ -159,6 +159,15 @@
<el-button type="primary" link size="small" @click="handleView(scope.row)"> <el-button type="primary" link size="small" @click="handleView(scope.row)">
查看 查看
</el-button> </el-button>
<el-button
v-if="canCleanupInvalidPayment(scope.row)"
type="danger"
link
size="small"
@click="handleCleanupInvalidPayment(scope.row)"
>
清理失效支付
</el-button>
<el-button <el-button
v-if="hasFinancialConfirmRole && scope.row.flow_status === 1 && scope.row.status !== 'completed'" v-if="hasFinancialConfirmRole && scope.row.flow_status === 1 && scope.row.status !== 'completed'"
type="success" type="success"
@ -661,7 +670,7 @@
<script setup> <script setup>
import { ref, onMounted, computed, nextTick } from 'vue' import { ref, onMounted, computed, nextTick } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, List } from '@element-plus/icons-vue' import { Search, Refresh, List } from '@element-plus/icons-vue'
import { paymentAPI, paymentCategoryAPI, budgetDataAPI, plannedExpenditureAPI, detailTableFieldAPI, departmentAPI, userAPI, permissionAPI, oaFlowAPI } from '@/utils/api' import { paymentAPI, paymentCategoryAPI, budgetDataAPI, plannedExpenditureAPI, detailTableFieldAPI, departmentAPI, userAPI, permissionAPI, oaFlowAPI } from '@/utils/api'
import request from '@/utils/request' import request from '@/utils/request'
@ -903,6 +912,42 @@ const handleView = (row) => {
window.open(url, '_blank') window.open(url, '_blank')
} }
const canCleanupInvalidPayment = (row) => {
return Boolean(row?.oa_flow_id) && !row?.flow_info && row?.status !== 'completed'
}
const handleCleanupInvalidPayment = async (row) => {
if (!row?.id) return
try {
await ElMessageBox.confirm(
`这会软删除支付单「${row.serial_number || row.id}」,用于清理已失效的 OA 流程关联。是否继续?`,
'清理失效支付',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
} catch (error) {
if (error === 'cancel') return
return
}
try {
const response = await paymentAPI.delete(row.id)
if (response.code === 0) {
ElMessage.success('失效支付已软删除')
await loadData()
return
}
ElMessage.error(response.msg || response.message || '清理失效支付失败')
} catch (error) {
console.error('清理失效支付失败:', error)
ElMessage.error(error.message || '清理失效支付失败')
}
}
// //
const loadApprovalFlowDetails = async (payment) => { const loadApprovalFlowDetails = async (payment) => {
if (!payment?.fields || !templateElements.value.length) return if (!payment?.fields || !templateElements.value.length) return
@ -2089,4 +2134,3 @@ onMounted(async () => {

Loading…
Cancel
Save