|
@@ -147,6 +147,8 @@
|
|
|
link
|
|
link
|
|
|
type="success"
|
|
type="success"
|
|
|
size="small"
|
|
size="small"
|
|
|
|
|
+ :disabled="isRowPassBlocked(row as Approval)"
|
|
|
|
|
+ :title="rowPassBlockReason(row as Approval) || undefined"
|
|
|
@click="onDecide(row as Approval, true)"
|
|
@click="onDecide(row as Approval, true)"
|
|
|
>
|
|
>
|
|
|
通过
|
|
通过
|
|
@@ -195,14 +197,14 @@
|
|
|
<el-descriptions-item label="申请人">{{ currentRow.applicant }}</el-descriptions-item>
|
|
<el-descriptions-item label="申请人">{{ currentRow.applicant }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="提交时间">{{ currentRow.createdAt }}</el-descriptions-item>
|
|
<el-descriptions-item label="提交时间">{{ currentRow.createdAt }}</el-descriptions-item>
|
|
|
<el-descriptions-item v-if="!isSpecialApproval(currentRow) && !isQualifyApproval(currentRow)" label="总发放金额" :span="2">
|
|
<el-descriptions-item v-if="!isSpecialApproval(currentRow) && !isQualifyApproval(currentRow)" label="总发放金额" :span="2">
|
|
|
- <strong>{{ formatAmount(detailAmountSummary.payable) }}</strong>
|
|
|
|
|
|
|
+ <strong>{{ formatAmount(detailBatchAmountSummary.payable) }}</strong>
|
|
|
<span class="muted" style="margin-left: 8px">
|
|
<span class="muted" style="margin-left: 8px">
|
|
|
- <template v-if="isAdjustDetailBatch">(调标差额批:应发=补发 {{ formatAmount(detailAmountSummary.supplement) }})</template>
|
|
|
|
|
|
|
+ <template v-if="isAdjustDetailBatch">(调标差额批:应发=补发 {{ formatAmount(detailBatchAmountSummary.supplement) }})</template>
|
|
|
<template v-else>
|
|
<template v-else>
|
|
|
- (标准 {{ formatAmount(detailAmountSummary.monthly) }}
|
|
|
|
|
- + 补发 {{ formatAmount(detailAmountSummary.supplement) }}
|
|
|
|
|
- + 丧葬 {{ formatAmount(detailAmountSummary.funeral) }}
|
|
|
|
|
- - 扣减 {{ formatAmount(detailAmountSummary.deduct) }})
|
|
|
|
|
|
|
+ (标准 {{ formatAmount(detailBatchAmountSummary.monthly) }}
|
|
|
|
|
+ + 补发 {{ formatAmount(detailBatchAmountSummary.supplement) }}
|
|
|
|
|
+ + 丧葬 {{ formatAmount(detailBatchAmountSummary.funeral) }}
|
|
|
|
|
+ - 扣减 {{ formatAmount(detailBatchAmountSummary.deduct) }})
|
|
|
</template>
|
|
</template>
|
|
|
</span>
|
|
</span>
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
@@ -242,6 +244,7 @@
|
|
|
{{ isQualifyApproval(currentRow) ? '申请人员' : '涉及人员' }}
|
|
{{ isQualifyApproval(currentRow) ? '申请人员' : '涉及人员' }}
|
|
|
<em>共 {{ detailPeople.length || (currentRow?.peopleCount ?? 0) }} 人</em>
|
|
<em>共 {{ detailPeople.length || (currentRow?.peopleCount ?? 0) }} 人</em>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <SensitiveRevealSwitch v-model="revealSensitive" />
|
|
|
</div>
|
|
</div>
|
|
|
<el-table
|
|
<el-table
|
|
|
v-loading="peopleLoading"
|
|
v-loading="peopleLoading"
|
|
@@ -252,8 +255,16 @@
|
|
|
max-height="240"
|
|
max-height="240"
|
|
|
>
|
|
>
|
|
|
<el-table-column prop="name" label="姓名" width="90" />
|
|
<el-table-column prop="name" label="姓名" width="90" />
|
|
|
- <el-table-column label="身份证" min-width="160">
|
|
|
|
|
- <template #default="{ row }">{{ maskIdCard(row.idCard) }}</template>
|
|
|
|
|
|
|
+ <el-table-column label="身份证" min-width="168">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <SensitiveValue
|
|
|
|
|
+ :key="`sp-id-${row.id}`"
|
|
|
|
|
+ :row-key="row.id"
|
|
|
|
|
+ :value="row.idCard"
|
|
|
|
|
+ kind="idCard"
|
|
|
|
|
+ :revealed="revealSensitive"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column prop="town" label="街镇" width="90" />
|
|
<el-table-column prop="town" label="街镇" width="90" />
|
|
|
<el-table-column prop="village" label="村居" width="90" />
|
|
<el-table-column prop="village" label="村居" width="90" />
|
|
@@ -286,21 +297,73 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div v-else class="approvals-view__people">
|
|
<div v-else class="approvals-view__people">
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ v-if="detailPassBlockAlert"
|
|
|
|
|
+ :title="detailPassBlockAlert"
|
|
|
|
|
+ type="warning"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ class="approvals-view__block-alert"
|
|
|
|
|
+ />
|
|
|
<div class="approvals-view__people-toolbar">
|
|
<div class="approvals-view__people-toolbar">
|
|
|
<div class="approvals-view__people-title">
|
|
<div class="approvals-view__people-title">
|
|
|
{{ isTownReview ? '村合作社提交名单' : '本镇人员列表' }}
|
|
{{ isTownReview ? '村合作社提交名单' : '本镇人员列表' }}
|
|
|
<span v-if="currentRow?.village">
|
|
<span v-if="currentRow?.village">
|
|
|
({{ currentRow.town }}{{ currentRow.village }} · 批次 {{ currentRow.batchNo }})
|
|
({{ currentRow.town }}{{ currentRow.village }} · 批次 {{ currentRow.batchNo }})
|
|
|
</span>
|
|
</span>
|
|
|
- <em>共 {{ detailPeople.length }} 人</em>
|
|
|
|
|
|
|
+ <em>共 {{ detailPeopleTotal || detailPeople.length }} 人</em>
|
|
|
<em class="approvals-view__people-amount">
|
|
<em class="approvals-view__people-amount">
|
|
|
- ,总发放 {{ formatAmount(detailAmountSummary.payable) }} 元
|
|
|
|
|
|
|
+ ,总发放 {{ formatAmount(detailBatchAmountSummary.payable) }} 元
|
|
|
</em>
|
|
</em>
|
|
|
- <em v-if="detailStatusFilter">,当前 {{ filteredDetailPeople.length }} 人</em>
|
|
|
|
|
- <em v-if="isTownReview && pendingQualifyCount > 0" class="approvals-view__people-warn">
|
|
|
|
|
- 待核验 {{ pendingQualifyCount }} 人,须核验后才能通过
|
|
|
|
|
|
|
+ <em v-if="detailStatusFilter || detailSearchName || detailSearchIdCard">
|
|
|
|
|
+ ,当前筛选 {{ detailPeopleTotal }} 人
|
|
|
</em>
|
|
</em>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="approvals-view__people-search">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="detailSearchName"
|
|
|
|
|
+ placeholder="姓名"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ style="width: 120px"
|
|
|
|
|
+ @keyup.enter="onDetailSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="detailSearchIdCard"
|
|
|
|
|
+ placeholder="身份证(可后几位)"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ style="width: 160px"
|
|
|
|
|
+ @keyup.enter="onDetailSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="detailAmountSort"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ placeholder="金额排序"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ style="width: 140px"
|
|
|
|
|
+ @change="onDetailSearch"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option label="总计金额↓" value="totalAmount:desc" />
|
|
|
|
|
+ <el-option label="总计金额↑" value="totalAmount:asc" />
|
|
|
|
|
+ <el-option label="补发金额↓" value="supplementAmount:desc" />
|
|
|
|
|
+ <el-option label="丧葬金额↓" value="funeralAmount:desc" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="detailAmountFilter"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ placeholder="金额筛选"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ style="width: 120px"
|
|
|
|
|
+ @change="onDetailSearch"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option label="仅有补发" value="supplement" />
|
|
|
|
|
+ <el-option label="仅有丧葬" value="funeral" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <el-button size="small" type="primary" @click="onDetailSearch">查询</el-button>
|
|
|
|
|
+ <el-button size="small" @click="resetDetailSearch">重置</el-button>
|
|
|
|
|
+ <SensitiveRevealSwitch v-model="revealSensitive" />
|
|
|
|
|
+ </div>
|
|
|
<el-select
|
|
<el-select
|
|
|
v-model="detailStatusFilter"
|
|
v-model="detailStatusFilter"
|
|
|
clearable
|
|
clearable
|
|
@@ -327,6 +390,7 @@
|
|
|
max-height="320"
|
|
max-height="320"
|
|
|
show-summary
|
|
show-summary
|
|
|
:summary-method="detailTableSummary"
|
|
:summary-method="detailTableSummary"
|
|
|
|
|
+ :row-class-name="detailPeopleRowClass"
|
|
|
>
|
|
>
|
|
|
<el-table-column prop="name" label="姓名" width="110">
|
|
<el-table-column prop="name" label="姓名" width="110">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
@@ -351,11 +415,27 @@
|
|
|
</el-tag>
|
|
</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="身份证" min-width="140">
|
|
|
|
|
- <template #default="{ row }">{{ maskIdCard(row.idCard) }}</template>
|
|
|
|
|
|
|
+ <el-table-column label="身份证" min-width="168">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <SensitiveValue
|
|
|
|
|
+ :key="`id-${row.id}`"
|
|
|
|
|
+ :row-key="row.id"
|
|
|
|
|
+ :value="row.idCard"
|
|
|
|
|
+ kind="idCard"
|
|
|
|
|
+ :revealed="revealSensitive"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="银行卡" min-width="140">
|
|
|
|
|
- <template #default="{ row }">{{ maskBankCard(row.bankCard) }}</template>
|
|
|
|
|
|
|
+ <el-table-column label="银行卡" min-width="160">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <SensitiveValue
|
|
|
|
|
+ :key="`bank-${row.id}`"
|
|
|
|
|
+ :row-key="row.id"
|
|
|
|
|
+ :value="row.bankCard"
|
|
|
|
|
+ kind="bankCard"
|
|
|
|
|
+ :revealed="revealSensitive"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column v-if="!isAdjustDetailBatch" label="标准金额" width="100" align="right">
|
|
<el-table-column v-if="!isAdjustDetailBatch" label="标准金额" width="100" align="right">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
@@ -467,8 +547,8 @@
|
|
|
<el-button
|
|
<el-button
|
|
|
type="primary"
|
|
type="primary"
|
|
|
:loading="deciding"
|
|
:loading="deciding"
|
|
|
- :disabled="townPassBlocked"
|
|
|
|
|
- :title="townPassBlockedReason || undefined"
|
|
|
|
|
|
|
+ :disabled="detailPassBlocked"
|
|
|
|
|
+ :title="detailPassBlockedReason || undefined"
|
|
|
@click="onDecide(currentRow!, true)"
|
|
@click="onDecide(currentRow!, true)"
|
|
|
>
|
|
>
|
|
|
通过
|
|
通过
|
|
@@ -545,7 +625,7 @@
|
|
|
>
|
|
>
|
|
|
<p class="approvals-view__reject-tip">
|
|
<p class="approvals-view__reject-tip">
|
|
|
<template v-if="isQualifyRejectTarget">
|
|
<template v-if="isQualifyRejectTarget">
|
|
|
- 「无资格」将人员标为异常待处理;「补材料」退回村合作社「待提交资格」后可重提。
|
|
|
|
|
|
|
+ 驳回后退回村合作社「待提交资格」,补充材料后可重新提交。
|
|
|
</template>
|
|
</template>
|
|
|
<template v-else-if="isTownReview && ineligibleMembers.length">
|
|
<template v-else-if="isTownReview && ineligibleMembers.length">
|
|
|
本批有 {{ ineligibleMembers.length }} 人已认定「无资格」({{
|
|
本批有 {{ ineligibleMembers.length }} 人已认定「无资格」({{
|
|
@@ -563,12 +643,6 @@
|
|
|
</template>
|
|
</template>
|
|
|
</p>
|
|
</p>
|
|
|
<el-form label-width="90px" size="small">
|
|
<el-form label-width="90px" size="small">
|
|
|
- <el-form-item v-if="isQualifyRejectTarget" label="驳回方式" required>
|
|
|
|
|
- <el-radio-group v-model="rejectQualifyMode">
|
|
|
|
|
- <el-radio value="supplement">驳回补材料</el-radio>
|
|
|
|
|
- <el-radio value="ineligible">认定无资格</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
<el-form-item label="驳回理由" required>
|
|
<el-form-item label="驳回理由" required>
|
|
|
<el-input
|
|
<el-input
|
|
|
v-model="rejectOpinion"
|
|
v-model="rejectOpinion"
|
|
@@ -610,7 +684,9 @@
|
|
|
</p>
|
|
</p>
|
|
|
<el-descriptions v-if="qualifyTarget" :column="1" border size="small" class="approvals-view__qualify-info">
|
|
<el-descriptions v-if="qualifyTarget" :column="1" border size="small" class="approvals-view__qualify-info">
|
|
|
<el-descriptions-item label="姓名">{{ qualifyTarget.name }}</el-descriptions-item>
|
|
<el-descriptions-item label="姓名">{{ qualifyTarget.name }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="身份证">{{ maskIdCard(qualifyTarget.idCard) }}</el-descriptions-item>
|
|
|
|
|
|
|
+ <el-descriptions-item label="身份证">
|
|
|
|
|
+ <SensitiveValue :value="qualifyTarget.idCard" kind="idCard" />
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
<el-descriptions-item label="村居">
|
|
<el-descriptions-item label="村居">
|
|
|
{{ qualifyTarget.town }}{{ qualifyTarget.village }}
|
|
{{ qualifyTarget.town }}{{ qualifyTarget.village }}
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
@@ -670,6 +746,7 @@ import {
|
|
|
fetchBatchMemberPeoplePage,
|
|
fetchBatchMemberPeoplePage,
|
|
|
fetchPersonDetail,
|
|
fetchPersonDetail,
|
|
|
fetchSpecialBizPerson,
|
|
fetchSpecialBizPerson,
|
|
|
|
|
+ fetchUnresolvedInsuranceCount,
|
|
|
reviewPersonQualify,
|
|
reviewPersonQualify,
|
|
|
uploadBizFile,
|
|
uploadBizFile,
|
|
|
} from '@/api/biz'
|
|
} from '@/api/biz'
|
|
@@ -678,13 +755,21 @@ import { useServerPagination } from '@/composables/useServerPagination'
|
|
|
import { useTownVillageOptions } from '@/composables/useTownVillageOptions'
|
|
import { useTownVillageOptions } from '@/composables/useTownVillageOptions'
|
|
|
import { guardBusy } from '@/composables/useSubmitLock'
|
|
import { guardBusy } from '@/composables/useSubmitLock'
|
|
|
import { useUserStore } from '@/stores/user'
|
|
import { useUserStore } from '@/stores/user'
|
|
|
-import { sumBatchMemberAmounts } from '@/utils/batchAmounts'
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ paymentSummaryFromApi,
|
|
|
|
|
+ sumBatchMemberAmounts,
|
|
|
|
|
+ type BatchAmountSummary,
|
|
|
|
|
+} from '@/utils/batchAmounts'
|
|
|
import { isYearlyAdjustBatchLike } from '@/utils/batchAdjust'
|
|
import { isYearlyAdjustBatchLike } from '@/utils/batchAdjust'
|
|
|
-import { formatAmount, maskBankCard, maskIdCard } from '@/utils/mask'
|
|
|
|
|
|
|
+import { formatAmount } from '@/utils/mask'
|
|
|
import type { Approval, BatchMemberPerson, Person } from '@/types/biz'
|
|
import type { Approval, BatchMemberPerson, Person } from '@/types/biz'
|
|
|
|
|
+import SensitiveValue from '@/components/common/SensitiveValue.vue'
|
|
|
|
|
+import SensitiveRevealSwitch from '@/components/common/SensitiveRevealSwitch.vue'
|
|
|
|
|
|
|
|
const userStore = useUserStore()
|
|
const userStore = useUserStore()
|
|
|
const isAdmin = computed(() => userStore.roleInfo?.key === 'admin')
|
|
const isAdmin = computed(() => userStore.roleInfo?.key === 'admin')
|
|
|
|
|
+/** 审批详情证件/卡号脱敏开关 */
|
|
|
|
|
+const revealSensitive = ref(false)
|
|
|
const {
|
|
const {
|
|
|
filterTown,
|
|
filterTown,
|
|
|
filterVillage,
|
|
filterVillage,
|
|
@@ -705,6 +790,10 @@ const detailVisible = ref(false)
|
|
|
const currentRow = ref<Approval | null>(null)
|
|
const currentRow = ref<Approval | null>(null)
|
|
|
const detailPeople = ref<BatchMemberPerson[]>([])
|
|
const detailPeople = ref<BatchMemberPerson[]>([])
|
|
|
const peopleLoading = ref(false)
|
|
const peopleLoading = ref(false)
|
|
|
|
|
+/** 发放明细分页接口回填的全量合计(不是本页合计) */
|
|
|
|
|
+const detailPaymentSummary = ref<BatchAmountSummary | null>(null)
|
|
|
|
|
+/** 分管领导:批次居保未闭环人数缓存(approvalId → count) */
|
|
|
|
|
+const unresolvedInsuranceByApproval = ref<Record<string, number>>({})
|
|
|
|
|
|
|
|
const isAdjustDetailBatch = computed(() =>
|
|
const isAdjustDetailBatch = computed(() =>
|
|
|
isYearlyAdjustBatchLike({
|
|
isYearlyAdjustBatchLike({
|
|
@@ -713,16 +802,35 @@ const isAdjustDetailBatch = computed(() =>
|
|
|
}),
|
|
}),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-const detailAmountSummary = computed(() =>
|
|
|
|
|
|
|
+/** 表格底部:仅本页合计 */
|
|
|
|
|
+const detailPageAmountSummary = computed(() =>
|
|
|
sumBatchMemberAmounts(detailPeople.value, { adjustOnly: isAdjustDetailBatch.value }),
|
|
sumBatchMemberAmounts(detailPeople.value, { adjustOnly: isAdjustDetailBatch.value }),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+/** 详情头/工具栏:全量合计(接口 summary → 列表行金额 → 本页兜底) */
|
|
|
|
|
+const detailBatchAmountSummary = computed((): BatchAmountSummary => {
|
|
|
|
|
+ if (detailPaymentSummary.value) return detailPaymentSummary.value
|
|
|
|
|
+ const row = currentRow.value
|
|
|
|
|
+ if (row && (row.totalAmount != null || row.peopleCount != null)) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ count: Number(row.peopleCount || 0),
|
|
|
|
|
+ monthly: 0,
|
|
|
|
|
+ oneTime: 0,
|
|
|
|
|
+ supplement: 0,
|
|
|
|
|
+ funeral: 0,
|
|
|
|
|
+ deduct: 0,
|
|
|
|
|
+ payable: Number(row.totalAmount || 0),
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return detailPageAmountSummary.value
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
function detailTableSummary(param: {
|
|
function detailTableSummary(param: {
|
|
|
columns: Array<{ label?: string; property?: string }>
|
|
columns: Array<{ label?: string; property?: string }>
|
|
|
}) {
|
|
}) {
|
|
|
- const sum = detailAmountSummary.value
|
|
|
|
|
|
|
+ const sum = detailPageAmountSummary.value
|
|
|
return param.columns.map((col, index) => {
|
|
return param.columns.map((col, index) => {
|
|
|
- if (index === 0) return '合计'
|
|
|
|
|
|
|
+ if (index === 0) return '本页合计'
|
|
|
const label = String(col.label || col.property || '')
|
|
const label = String(col.label || col.property || '')
|
|
|
if (label.includes('标准')) return formatAmount(sum.monthly)
|
|
if (label.includes('标准')) return formatAmount(sum.monthly)
|
|
|
if (label.includes('补发')) return formatAmount(sum.supplement)
|
|
if (label.includes('补发')) return formatAmount(sum.supplement)
|
|
@@ -784,6 +892,7 @@ function onRejectDialogClosed() {
|
|
|
rejectRawFiles.value = []
|
|
rejectRawFiles.value = []
|
|
|
rejectTarget.value = null
|
|
rejectTarget.value = null
|
|
|
rejectBatchMode.value = false
|
|
rejectBatchMode.value = false
|
|
|
|
|
+ rejectQualifyMode.value = 'supplement'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function onPassDialogClosed() {
|
|
function onPassDialogClosed() {
|
|
@@ -863,6 +972,12 @@ async function syncScopeTown() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
const detailStatusFilter = ref('')
|
|
const detailStatusFilter = ref('')
|
|
|
|
|
+const detailSearchName = ref('')
|
|
|
|
|
+const detailSearchIdCard = ref('')
|
|
|
|
|
+/** totalAmount:desc | supplementAmount:desc | funeralAmount:desc ... */
|
|
|
|
|
+const detailAmountSort = ref('')
|
|
|
|
|
+/** supplement | funeral */
|
|
|
|
|
+const detailAmountFilter = ref('')
|
|
|
const detailStatusOptions = computed(() => {
|
|
const detailStatusOptions = computed(() => {
|
|
|
const set = new Set<string>()
|
|
const set = new Set<string>()
|
|
|
for (const p of detailPeople.value) {
|
|
for (const p of detailPeople.value) {
|
|
@@ -882,6 +997,20 @@ function resetDetailPeoplePage() {
|
|
|
detailPeoplePage.value = 1
|
|
detailPeoplePage.value = 1
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function onDetailSearch() {
|
|
|
|
|
+ detailPeoplePage.value = 1
|
|
|
|
|
+ void reloadDetailPeople()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetDetailSearch() {
|
|
|
|
|
+ detailSearchName.value = ''
|
|
|
|
|
+ detailSearchIdCard.value = ''
|
|
|
|
|
+ detailAmountSort.value = ''
|
|
|
|
|
+ detailAmountFilter.value = ''
|
|
|
|
|
+ detailPeoplePage.value = 1
|
|
|
|
|
+ void reloadDetailPeople()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/** 按角色限定审批节点 */
|
|
/** 按角色限定审批节点 */
|
|
|
const approvalNode = computed(() => {
|
|
const approvalNode = computed(() => {
|
|
|
if (userStore.currentRole === 'town') return '镇经发中心初审'
|
|
if (userStore.currentRole === 'town') return '镇经发中心初审'
|
|
@@ -903,7 +1032,7 @@ const {
|
|
|
selectedIds.value = []
|
|
selectedIds.value = []
|
|
|
const role = userStore.roleInfo
|
|
const role = userStore.roleInfo
|
|
|
const isPending = activeTab.value === 'pending'
|
|
const isPending = activeTab.value === 'pending'
|
|
|
- return fetchApprovals({
|
|
|
|
|
|
|
+ const page = await fetchApprovals({
|
|
|
tab: isPending ? 'pending' : 'history',
|
|
tab: isPending ? 'pending' : 'history',
|
|
|
node: approvalNode.value,
|
|
node: approvalNode.value,
|
|
|
town: isTownReview.value || isLeaderReview.value ? role?.town : filterTown.value || undefined,
|
|
town: isTownReview.value || isLeaderReview.value ? role?.town : filterTown.value || undefined,
|
|
@@ -913,6 +1042,8 @@ const {
|
|
|
pageNum,
|
|
pageNum,
|
|
|
pageSize,
|
|
pageSize,
|
|
|
})
|
|
})
|
|
|
|
|
+ void refreshUnresolvedInsuranceForList(page.list || [])
|
|
|
|
|
+ return page
|
|
|
})
|
|
})
|
|
|
const list = pagedList
|
|
const list = pagedList
|
|
|
|
|
|
|
@@ -926,28 +1057,102 @@ const pageTitle = computed(() => {
|
|
|
/** 当前发放周期对应批次号 */
|
|
/** 当前发放周期对应批次号 */
|
|
|
const currentBatchNo = computed(() => userStore.currentMonth.replace('-', ''))
|
|
const currentBatchNo = computed(() => userStore.currentMonth.replace('-', ''))
|
|
|
|
|
|
|
|
-/** 详情中仍需资格核验的人数(退养资格字段为空) */
|
|
|
|
|
|
|
+/** 详情中仍需资格核验的人数(仅镇经发场景使用) */
|
|
|
const pendingQualifyCount = computed(
|
|
const pendingQualifyCount = computed(
|
|
|
() => detailPeople.value.filter((p) => needsQualifyReview(p)).length,
|
|
() => detailPeople.value.filter((p) => needsQualifyReview(p)).length,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-const townPassBlockedReason = computed(() => {
|
|
|
|
|
- if (isQualifyApproval(currentRow.value)) return ''
|
|
|
|
|
- if (hasIneligibleMembers.value) {
|
|
|
|
|
- return `存在「无资格」人员 ${ineligibleMembers.value.length} 人,只能驳回由村处理`
|
|
|
|
|
|
|
+/** 已认定「无资格」的人员:镇经发批次只能驳回 */
|
|
|
|
|
+const ineligibleMembers = computed(() =>
|
|
|
|
|
+ detailPeople.value.filter((p) => p.retireQualifyType === '无资格' || p.status === '无资格'),
|
|
|
|
|
+)
|
|
|
|
|
+const hasIneligibleMembers = computed(() => ineligibleMembers.value.length > 0)
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 详情弹窗「通过」禁用原因:
|
|
|
|
|
+ * - 镇经发:无资格 / 未核验资格(资格审批本身除外)
|
|
|
|
|
+ * - 分管领导:居保未闭环(资格核验与领导无关,绝不拦)
|
|
|
|
|
+ */
|
|
|
|
|
+const detailPassBlockedReason = computed(() => {
|
|
|
|
|
+ const row = currentRow.value
|
|
|
|
|
+ if (!row || isQualifyApproval(row) || isSpecialApproval(row)) return ''
|
|
|
|
|
+ if (isTownReview.value) {
|
|
|
|
|
+ if (hasIneligibleMembers.value) {
|
|
|
|
|
+ return `存在「无资格」人员 ${ineligibleMembers.value.length} 人,只能驳回由村处理`
|
|
|
|
|
+ }
|
|
|
|
|
+ if (pendingQualifyCount.value > 0) {
|
|
|
|
|
+ return `有 ${pendingQualifyCount.value} 人退养资格尚未核验,请先在名单中点「核验资格」`
|
|
|
|
|
+ }
|
|
|
|
|
+ return ''
|
|
|
}
|
|
}
|
|
|
- if (pendingQualifyCount.value > 0) {
|
|
|
|
|
- return `有 ${pendingQualifyCount.value} 人退养资格尚未核验,请先点「核验资格」`
|
|
|
|
|
|
|
+ if (isLeaderReview.value) {
|
|
|
|
|
+ const n = unresolvedInsuranceByApproval.value[String(row.id)] || 0
|
|
|
|
|
+ if (n > 0) {
|
|
|
|
|
+ return `仍有 ${n} 人居保未匹配/未通过,请先完成居保匹配或人工确认后再通过`
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
return ''
|
|
return ''
|
|
|
})
|
|
})
|
|
|
-const townPassBlocked = computed(() => Boolean(townPassBlockedReason.value))
|
|
|
|
|
|
|
+const detailPassBlocked = computed(() => Boolean(detailPassBlockedReason.value))
|
|
|
|
|
|
|
|
-/** 已认定「无资格」的人员:批次只能驳回 */
|
|
|
|
|
-const ineligibleMembers = computed(() =>
|
|
|
|
|
- detailPeople.value.filter((p) => p.retireQualifyType === '无资格' || p.status === '无资格'),
|
|
|
|
|
-)
|
|
|
|
|
-const hasIneligibleMembers = computed(() => ineligibleMembers.value.length > 0)
|
|
|
|
|
|
|
+const detailPassBlockAlert = computed(() => {
|
|
|
|
|
+ if (!detailPassBlockedReason.value) return ''
|
|
|
|
|
+ if (isTownReview.value && pendingQualifyCount.value > 0) {
|
|
|
|
|
+ return `${detailPassBlockedReason.value}(当前页可见待核验人员已标红,请先处理)`
|
|
|
|
|
+ }
|
|
|
|
|
+ return detailPassBlockedReason.value
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+function rowPassBlockReason(row: Approval): string {
|
|
|
|
|
+ if (row.status !== '待审批') return ''
|
|
|
|
|
+ if (isQualifyApproval(row) || isSpecialApproval(row)) return ''
|
|
|
|
|
+ if (isTownReview.value) {
|
|
|
|
|
+ // 详情已打开且是同一条:与弹窗共用资格拦截
|
|
|
|
|
+ if (detailVisible.value && currentRow.value?.id === row.id) {
|
|
|
|
|
+ return detailPassBlockedReason.value
|
|
|
|
|
+ }
|
|
|
|
|
+ return ''
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isLeaderReview.value) {
|
|
|
|
|
+ const n = unresolvedInsuranceByApproval.value[String(row.id)] || 0
|
|
|
|
|
+ if (n > 0) {
|
|
|
|
|
+ return `仍有 ${n} 人居保未匹配/未通过,不可通过`
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isRowPassBlocked(row: Approval) {
|
|
|
|
|
+ return Boolean(rowPassBlockReason(row))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function detailPeopleRowClass({ row }: { row: BatchMemberPerson }) {
|
|
|
|
|
+ if (isTownReview.value && needsQualifyReview(row)) {
|
|
|
|
|
+ return 'approvals-view__row--qualify-pending'
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isTownReview.value && (row.retireQualifyType === '无资格' || row.status === '无资格')) {
|
|
|
|
|
+ return 'approvals-view__row--ineligible'
|
|
|
|
|
+ }
|
|
|
|
|
+ return ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function refreshUnresolvedInsuranceForList(rows: Approval[]) {
|
|
|
|
|
+ if (!isLeaderReview.value) return
|
|
|
|
|
+ const next: Record<string, number> = { ...unresolvedInsuranceByApproval.value }
|
|
|
|
|
+ await Promise.all(
|
|
|
|
|
+ rows.map(async (row) => {
|
|
|
|
|
+ if (isQualifyApproval(row) || isSpecialApproval(row)) return
|
|
|
|
|
+ const batchId = row.batchId || batchIdOfApproval(row)
|
|
|
|
|
+ if (!batchId) return
|
|
|
|
|
+ try {
|
|
|
|
|
+ next[String(row.id)] = await fetchUnresolvedInsuranceCount(batchId)
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // 忽略单条失败,不挡列表
|
|
|
|
|
+ }
|
|
|
|
|
+ }),
|
|
|
|
|
+ )
|
|
|
|
|
+ unresolvedInsuranceByApproval.value = next
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
function onSelectionChange(rows: Approval[]) {
|
|
function onSelectionChange(rows: Approval[]) {
|
|
|
selectedIds.value = rows.map((r) => r.id)
|
|
selectedIds.value = rows.map((r) => r.id)
|
|
@@ -1046,7 +1251,10 @@ async function openDetail(row: Approval) {
|
|
|
currentRow.value = { ...row }
|
|
currentRow.value = { ...row }
|
|
|
detailVisible.value = true
|
|
detailVisible.value = true
|
|
|
detailPeople.value = []
|
|
detailPeople.value = []
|
|
|
|
|
+ detailPaymentSummary.value = null
|
|
|
detailStatusFilter.value = ''
|
|
detailStatusFilter.value = ''
|
|
|
|
|
+ detailSearchName.value = ''
|
|
|
|
|
+ detailSearchIdCard.value = ''
|
|
|
peopleLoading.value = true
|
|
peopleLoading.value = true
|
|
|
try {
|
|
try {
|
|
|
if (isQualifyApproval(row) || isSpecialApproval(row)) {
|
|
if (isQualifyApproval(row) || isSpecialApproval(row)) {
|
|
@@ -1103,6 +1311,17 @@ async function openDetail(row: Approval) {
|
|
|
detailPeople.value = []
|
|
detailPeople.value = []
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ if (isLeaderReview.value) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const n = await fetchUnresolvedInsuranceCount(batchId)
|
|
|
|
|
+ unresolvedInsuranceByApproval.value = {
|
|
|
|
|
+ ...unresolvedInsuranceByApproval.value,
|
|
|
|
|
+ [String(row.id)]: n,
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // ignore
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
const peoplePage = await fetchBatchMemberPeoplePage(batchId, {
|
|
const peoplePage = await fetchBatchMemberPeoplePage(batchId, {
|
|
|
batchNo: row.batchNo,
|
|
batchNo: row.batchNo,
|
|
|
town: row.town,
|
|
town: row.town,
|
|
@@ -1110,21 +1329,22 @@ async function openDetail(row: Approval) {
|
|
|
pruneDefaults: false,
|
|
pruneDefaults: false,
|
|
|
pageNum: 1,
|
|
pageNum: 1,
|
|
|
pageSize: detailPeoplePageSize.value,
|
|
pageSize: detailPeoplePageSize.value,
|
|
|
|
|
+ name: detailSearchName.value.trim() || undefined,
|
|
|
|
|
+ idCard: detailSearchIdCard.value.trim() || undefined,
|
|
|
})
|
|
})
|
|
|
detailPeople.value = peoplePage.list
|
|
detailPeople.value = peoplePage.list
|
|
|
detailPeopleTotal.value = peoplePage.total
|
|
detailPeopleTotal.value = peoplePage.total
|
|
|
detailPeoplePage.value = 1
|
|
detailPeoplePage.value = 1
|
|
|
- const sum = sumBatchMemberAmounts(detailPeople.value, {
|
|
|
|
|
- adjustOnly: isYearlyAdjustBatchLike({
|
|
|
|
|
- batchNo: row.batchNo,
|
|
|
|
|
- remarks: (row as any).remarks || (row as any).remark,
|
|
|
|
|
- }),
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const apiSum = paymentSummaryFromApi(peoplePage.summary)
|
|
|
|
|
+ detailPaymentSummary.value = apiSum
|
|
|
if (currentRow.value) {
|
|
if (currentRow.value) {
|
|
|
currentRow.value = {
|
|
currentRow.value = {
|
|
|
...currentRow.value,
|
|
...currentRow.value,
|
|
|
- peopleCount: peoplePage.total || currentRow.value.peopleCount,
|
|
|
|
|
- totalAmount: currentRow.value.totalAmount ?? sum.payable,
|
|
|
|
|
|
|
+ peopleCount: apiSum?.count || peoplePage.total || currentRow.value.peopleCount,
|
|
|
|
|
+ totalAmount:
|
|
|
|
|
+ apiSum?.payable ??
|
|
|
|
|
+ currentRow.value.totalAmount ??
|
|
|
|
|
+ 0,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} finally {
|
|
} finally {
|
|
@@ -1135,7 +1355,10 @@ async function openDetail(row: Approval) {
|
|
|
function onDetailClosed() {
|
|
function onDetailClosed() {
|
|
|
currentRow.value = null
|
|
currentRow.value = null
|
|
|
detailPeople.value = []
|
|
detailPeople.value = []
|
|
|
|
|
+ detailPaymentSummary.value = null
|
|
|
detailStatusFilter.value = ''
|
|
detailStatusFilter.value = ''
|
|
|
|
|
+ detailSearchName.value = ''
|
|
|
|
|
+ detailSearchIdCard.value = ''
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function doDecide(
|
|
async function doDecide(
|
|
@@ -1190,6 +1413,28 @@ async function doDecide(
|
|
|
|
|
|
|
|
async function townPassBlockReason(row: Approval, people?: Person[]): Promise<string | null> {
|
|
async function townPassBlockReason(row: Approval, people?: Person[]): Promise<string | null> {
|
|
|
if (isSpecialApproval(row) || isQualifyApproval(row)) return null
|
|
if (isSpecialApproval(row) || isQualifyApproval(row)) return null
|
|
|
|
|
+ // 分管领导:只拦居保未闭环;资格核验不属于领导职责
|
|
|
|
|
+ if (isLeaderReview.value || row.node.includes('分管领导')) {
|
|
|
|
|
+ const cached = unresolvedInsuranceByApproval.value[String(row.id)]
|
|
|
|
|
+ if (cached != null && cached > 0) {
|
|
|
|
|
+ return `仍有 ${cached} 人居保未匹配/未通过,请先完成居保匹配或人工确认后再通过`
|
|
|
|
|
+ }
|
|
|
|
|
+ const batchId = row.batchId || batchIdOfApproval(row)
|
|
|
|
|
+ if (!batchId) return null
|
|
|
|
|
+ try {
|
|
|
|
|
+ const n = await fetchUnresolvedInsuranceCount(batchId)
|
|
|
|
|
+ unresolvedInsuranceByApproval.value = {
|
|
|
|
|
+ ...unresolvedInsuranceByApproval.value,
|
|
|
|
|
+ [String(row.id)]: n,
|
|
|
|
|
+ }
|
|
|
|
|
+ if (n > 0) {
|
|
|
|
|
+ return `仍有 ${n} 人居保未匹配/未通过,请先完成居保匹配或人工确认后再通过`
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // ignore
|
|
|
|
|
+ }
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
if (!(isTownReview.value || row.node.includes('镇经发'))) return null
|
|
if (!(isTownReview.value || row.node.includes('镇经发'))) return null
|
|
|
let list = people
|
|
let list = people
|
|
|
if (!list) {
|
|
if (!list) {
|
|
@@ -1248,6 +1493,7 @@ async function onDecide(row: Approval, pass: boolean) {
|
|
|
}
|
|
}
|
|
|
rejectTarget.value = row
|
|
rejectTarget.value = row
|
|
|
rejectBatchMode.value = false
|
|
rejectBatchMode.value = false
|
|
|
|
|
+ rejectQualifyMode.value = 'supplement'
|
|
|
rejectFileList.value = []
|
|
rejectFileList.value = []
|
|
|
rejectRawFiles.value = []
|
|
rejectRawFiles.value = []
|
|
|
rejectOpinion.value = hasIneligibleMembers.value
|
|
rejectOpinion.value = hasIneligibleMembers.value
|
|
@@ -1333,6 +1579,7 @@ async function onBatchDecide(pass: boolean) {
|
|
|
if (!pass) {
|
|
if (!pass) {
|
|
|
rejectBatchMode.value = true
|
|
rejectBatchMode.value = true
|
|
|
rejectTarget.value = null
|
|
rejectTarget.value = null
|
|
|
|
|
+ rejectQualifyMode.value = 'supplement'
|
|
|
rejectOpinion.value = ''
|
|
rejectOpinion.value = ''
|
|
|
rejectFileList.value = []
|
|
rejectFileList.value = []
|
|
|
rejectRawFiles.value = []
|
|
rejectRawFiles.value = []
|
|
@@ -1373,9 +1620,26 @@ async function reloadDetailPeople() {
|
|
|
pruneDefaults: false,
|
|
pruneDefaults: false,
|
|
|
pageNum: detailPeoplePage.value,
|
|
pageNum: detailPeoplePage.value,
|
|
|
pageSize: detailPeoplePageSize.value,
|
|
pageSize: detailPeoplePageSize.value,
|
|
|
|
|
+ name: detailSearchName.value.trim() || undefined,
|
|
|
|
|
+ idCard: detailSearchIdCard.value.trim() || undefined,
|
|
|
|
|
+ sortBy: detailAmountSort.value ? detailAmountSort.value.split(':')[0] : undefined,
|
|
|
|
|
+ sortOrder: detailAmountSort.value ? detailAmountSort.value.split(':')[1] : undefined,
|
|
|
|
|
+ onlyFuneral: detailAmountFilter.value === 'funeral' || undefined,
|
|
|
|
|
+ onlySupplement: detailAmountFilter.value === 'supplement' || undefined,
|
|
|
})
|
|
})
|
|
|
detailPeople.value = peoplePage.list
|
|
detailPeople.value = peoplePage.list
|
|
|
detailPeopleTotal.value = peoplePage.total
|
|
detailPeopleTotal.value = peoplePage.total
|
|
|
|
|
+ const apiSum = paymentSummaryFromApi(peoplePage.summary)
|
|
|
|
|
+ if (apiSum) {
|
|
|
|
|
+ detailPaymentSummary.value = apiSum
|
|
|
|
|
+ if (currentRow.value) {
|
|
|
|
|
+ currentRow.value = {
|
|
|
|
|
+ ...currentRow.value,
|
|
|
|
|
+ peopleCount: apiSum.count || peoplePage.total || currentRow.value.peopleCount,
|
|
|
|
|
+ totalAmount: apiSum.payable,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
} finally {
|
|
} finally {
|
|
|
peopleLoading.value = false
|
|
peopleLoading.value = false
|
|
|
}
|
|
}
|
|
@@ -1429,14 +1693,35 @@ watch(
|
|
|
.approvals-view__people {
|
|
.approvals-view__people {
|
|
|
margin-top: 16px;
|
|
margin-top: 16px;
|
|
|
|
|
|
|
|
|
|
+ .approvals-view__block-alert {
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.approvals-view__row--qualify-pending) {
|
|
|
|
|
+ --el-table-tr-bg-color: #fff7e6;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.approvals-view__row--ineligible) {
|
|
|
|
|
+ --el-table-tr-bg-color: #fff1f0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
&-toolbar {
|
|
&-toolbar {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
gap: 12px;
|
|
gap: 12px;
|
|
|
margin-bottom: 8px;
|
|
margin-bottom: 8px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ &-search {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
&-title {
|
|
&-title {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
min-width: 0;
|
|
min-width: 0;
|