|
|
@@ -14,7 +14,12 @@ import {
|
|
|
TOWN_REVIEW_TIPS,
|
|
|
VILLAGE_WORKBENCH_TIPS,
|
|
|
} from '@/utils/buildDashboard'
|
|
|
-import { batchStatusLabel, processActionLabelOf, roleLabelOf } from '@/utils/statusLabels'
|
|
|
+import {
|
|
|
+ batchStatusLabel,
|
|
|
+ personnelStatusLabel,
|
|
|
+ processActionLabelOf,
|
|
|
+ roleLabelOf,
|
|
|
+} from '@/utils/statusLabels'
|
|
|
import type {
|
|
|
AccountItem,
|
|
|
AlertItem,
|
|
|
@@ -142,6 +147,47 @@ function seriesToHalfYearCharts(series: MonthStatRow[] | undefined, months = 6)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/** 当前发放年 1 月~当前月;缺月补 0,不混在册人数 */
|
|
|
+function seriesToYearMonthCharts(series: MonthStatRow[] | undefined, payMonth: string) {
|
|
|
+ const ym = String(payMonth || '').replace('-', '')
|
|
|
+ const year = ym.slice(0, 4)
|
|
|
+ const endMonth = Math.min(12, Math.max(1, Number(ym.slice(4, 6) || 12)))
|
|
|
+ const byMonth = new Map<string, MonthStatRow>()
|
|
|
+ for (const m of series || []) {
|
|
|
+ const key = String(m.payMonth || '').replace('-', '')
|
|
|
+ if (key) byMonth.set(key, m)
|
|
|
+ }
|
|
|
+ const people: { name: string; value: number }[] = []
|
|
|
+ const amount: { name: string; value: number }[] = []
|
|
|
+ for (let i = 1; i <= endMonth; i++) {
|
|
|
+ const key = `${year}${String(i).padStart(2, '0')}`
|
|
|
+ const row = byMonth.get(key)
|
|
|
+ const name = `${year}-${String(i).padStart(2, '0')}`
|
|
|
+ people.push({ name, value: Number(row?.peopleCount || 0) })
|
|
|
+ amount.push({ name, value: Number(row?.totalAmount || 0) })
|
|
|
+ }
|
|
|
+ return { people, amount }
|
|
|
+}
|
|
|
+
|
|
|
+/** 近 3 个自然年:同年各月发放人数/金额相加,缺口年 0 */
|
|
|
+function seriesToYearCharts(series: MonthStatRow[] | undefined, payMonth: string) {
|
|
|
+ const endYear = Number(String(payMonth || '').replace('-', '').slice(0, 4)) || new Date().getFullYear()
|
|
|
+ const years = [endYear - 2, endYear - 1, endYear]
|
|
|
+ const agg = new Map<number, { people: number; amount: number }>()
|
|
|
+ for (const y of years) agg.set(y, { people: 0, amount: 0 })
|
|
|
+ for (const m of series || []) {
|
|
|
+ const y = Number(String(m.payMonth || '').replace('-', '').slice(0, 4))
|
|
|
+ const row = agg.get(y)
|
|
|
+ if (!row) continue
|
|
|
+ row.people += Number(m.peopleCount || 0)
|
|
|
+ row.amount += Number(m.totalAmount || 0)
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ people: years.map((y) => ({ name: `${y}年`, value: agg.get(y)?.people || 0 })),
|
|
|
+ amount: years.map((y) => ({ name: `${y}年`, value: agg.get(y)?.amount || 0 })),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/** 本批次各村发放人数/金额(镇级工作台) */
|
|
|
async function buildTownVillageChartsFromApi(townId: string, payMonth: string) {
|
|
|
await ensureVillages(townId)
|
|
|
@@ -193,6 +239,10 @@ function emptyDashboard(role: ReturnType<typeof getAppConfig>['roles'][0], payMo
|
|
|
statusPieYear: [],
|
|
|
todos: [],
|
|
|
recentAlerts: [],
|
|
|
+ monthPeopleChart: [],
|
|
|
+ monthAmountChart: [],
|
|
|
+ yearPeopleChart: [],
|
|
|
+ yearAmountChart: [],
|
|
|
villageTips: role.village ? VILLAGE_WORKBENCH_TIPS : undefined,
|
|
|
districtOverview: isDistrict
|
|
|
? {
|
|
|
@@ -247,6 +297,7 @@ type WorkbenchApi = {
|
|
|
}>
|
|
|
villageCharts?: Array<{ villageId?: string; peopleCount?: number; amount?: number | string }>
|
|
|
batchStatusCounts?: Record<string, number>
|
|
|
+ personnelStatusCounts?: Record<string, number>
|
|
|
recentAlerts?: Record<string, unknown>[]
|
|
|
monthBatches?: Record<string, unknown>[]
|
|
|
todos?: Array<{
|
|
|
@@ -356,6 +407,7 @@ async function loadWorkbenchApi(
|
|
|
villageId?: string,
|
|
|
months = 6,
|
|
|
roleKey?: string,
|
|
|
+ skipPaymentScope = false,
|
|
|
): Promise<WorkbenchApi | null> {
|
|
|
const wb = await get<WorkbenchApi>(
|
|
|
'/dashboard/workbench',
|
|
|
@@ -366,6 +418,7 @@ async function loadWorkbenchApi(
|
|
|
months,
|
|
|
operator: operatorOf(),
|
|
|
roleKey: roleKey || getAppConfig().currentRole,
|
|
|
+ skipPaymentScope: skipPaymentScope || undefined,
|
|
|
},
|
|
|
{ silent: true, timeout: 180_000 },
|
|
|
).catch(() => null)
|
|
|
@@ -408,9 +461,39 @@ export async function markWorkbenchTodoRead(todoKey: string) {
|
|
|
}
|
|
|
|
|
|
/** —— 工作台:优先单接口聚合;旧后台无 workbench 时自动降级 —— */
|
|
|
+export async function fetchTownVillagePayTable(params: {
|
|
|
+ roleKey: string
|
|
|
+ currentMonth: string
|
|
|
+}): Promise<{ people: Array<{ name: string; value: number }>; amount: Array<{ name: string; value: number }> }> {
|
|
|
+ await ensureTowns()
|
|
|
+ const role =
|
|
|
+ getAppConfig().roles.find((r) => r.key === params.roleKey) || getAppConfig().roles[0]
|
|
|
+ const { townId } = await resolveScopeIds(role.town, undefined)
|
|
|
+ if (townId) await ensureVillages(townId)
|
|
|
+ const payMonth = monthToPayMonth(params.currentMonth)
|
|
|
+ const rows = await get<WorkbenchApi['villageCharts']>(
|
|
|
+ '/dashboard/village-charts',
|
|
|
+ { payMonth, townId: townId || undefined },
|
|
|
+ { silent: true, timeout: 180_000 },
|
|
|
+ ).catch(() => null)
|
|
|
+ const list = rows || []
|
|
|
+ return {
|
|
|
+ people: list.map((v) => ({
|
|
|
+ name: regionName(String(v.villageId || '')) || String(v.villageId || '未知村'),
|
|
|
+ value: Number(v.peopleCount || 0),
|
|
|
+ })),
|
|
|
+ amount: list.map((v) => ({
|
|
|
+ name: regionName(String(v.villageId || '')) || String(v.villageId || '未知村'),
|
|
|
+ value: Number(v.amount || 0),
|
|
|
+ })),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
export async function fetchDashboard(params: {
|
|
|
roleKey: string
|
|
|
currentMonth: string
|
|
|
+ /** 首屏跳过发放明细全量,村表再补一次 */
|
|
|
+ light?: boolean
|
|
|
}): Promise<DashboardData> {
|
|
|
await ensureTowns()
|
|
|
const role =
|
|
|
@@ -434,8 +517,9 @@ export async function fetchDashboard(params: {
|
|
|
payMonth,
|
|
|
townId || undefined,
|
|
|
villageId || undefined,
|
|
|
- 6,
|
|
|
+ 12,
|
|
|
params.roleKey,
|
|
|
+ !!params.light,
|
|
|
)
|
|
|
|
|
|
if (!wb) return base
|
|
|
@@ -443,16 +527,45 @@ export async function fetchDashboard(params: {
|
|
|
const summary = wb.summary
|
|
|
const region = wb.region
|
|
|
const halfYear = seriesToHalfYearCharts(region?.series, 6)
|
|
|
+ const yearMonth = seriesToYearMonthCharts(region?.series, payMonth)
|
|
|
+ const yearTotal = seriesToYearCharts(region?.series, payMonth)
|
|
|
+ base.monthPeopleChart = yearMonth.people
|
|
|
+ base.monthAmountChart = yearMonth.amount
|
|
|
+ base.yearPeopleChart = yearTotal.people
|
|
|
+ base.yearAmountChart = yearTotal.amount
|
|
|
let regionPeople = Number(region?.currentMonth?.peopleCount || 0)
|
|
|
let regionAmount = Number(region?.currentMonth?.totalAmount || 0)
|
|
|
if (regionPeople <= 0) regionPeople = Number(wb.monthPeopleCount || 0)
|
|
|
if (regionAmount <= 0) regionAmount = Number(wb.monthAmount || 0)
|
|
|
+ // 村合作社:发放明细挂全区/镇批,村粒度 region 常为 0;与镇展板各村表同源回切本村
|
|
|
+ if (
|
|
|
+ (role.key === 'village' || role.village) &&
|
|
|
+ townId &&
|
|
|
+ villageId &&
|
|
|
+ regionPeople <= 0
|
|
|
+ ) {
|
|
|
+ const townWb = await loadWorkbenchApi(payMonth, townId, undefined, 12, 'town', true)
|
|
|
+ const row = (townWb?.villageCharts || []).find((v) => {
|
|
|
+ const id = String(v.villageId || '')
|
|
|
+ const name = regionName(id)
|
|
|
+ return id === villageId || id === role.village || name === role.village || name === regionName(villageId)
|
|
|
+ })
|
|
|
+ if (row) {
|
|
|
+ regionPeople = Number(row.peopleCount || 0)
|
|
|
+ regionAmount = Number(row.amount || 0)
|
|
|
+ }
|
|
|
+ }
|
|
|
// 发放人数/金额禁止回退到在册人数或镇级汇总,避免村级卡片 1367 人配上被截断的 53 万
|
|
|
if (halfYear.people.length) {
|
|
|
const lastIdx = halfYear.people.length - 1
|
|
|
if (regionPeople > 0) halfYear.people[lastIdx].value = regionPeople
|
|
|
if (regionAmount > 0) halfYear.amount[lastIdx].value = regionAmount
|
|
|
}
|
|
|
+ if (yearMonth.people.length) {
|
|
|
+ const lastIdx = yearMonth.people.length - 1
|
|
|
+ if (regionPeople > 0) yearMonth.people[lastIdx].value = regionPeople
|
|
|
+ if (regionAmount > 0) yearMonth.amount[lastIdx].value = regionAmount
|
|
|
+ }
|
|
|
|
|
|
const pendingAlert = Number(summary?.pendingAlertCount || 0)
|
|
|
const pendingTown = Number(wb.pendingTownApproveCount || 0)
|
|
|
@@ -471,15 +584,25 @@ export async function fetchDashboard(params: {
|
|
|
base.batchInfo.alertCount = pendingAlert
|
|
|
|
|
|
const monthBatches = (wb.monthBatches || []).map(mapVillageBatch)
|
|
|
- const statusCounts = wb.batchStatusCounts || {}
|
|
|
+ const personnelCounts = wb.personnelStatusCounts || {}
|
|
|
+ const statusCounts = Object.keys(personnelCounts).length
|
|
|
+ ? personnelCounts
|
|
|
+ : wb.batchStatusCounts || {}
|
|
|
+ const usePersonnelPie = Object.keys(personnelCounts).length > 0
|
|
|
base.statusPieMonth = Object.entries(statusCounts)
|
|
|
.map(([code, value]) => ({
|
|
|
- name: batchStatusLabel(code),
|
|
|
+ name: usePersonnelPie ? personnelStatusLabel(code) || code : batchStatusLabel(code),
|
|
|
value: Number(value) || 0,
|
|
|
}))
|
|
|
.filter((i) => i.value > 0)
|
|
|
base.statusPieYear = base.statusPieMonth
|
|
|
- base.recentAlerts = (wb.recentAlerts || []).map(mapAlert)
|
|
|
+ base.recentAlerts = (wb.recentAlerts || [])
|
|
|
+ .map(mapAlert)
|
|
|
+ .filter((a) => {
|
|
|
+ if (role.key === 'village') return a.type === '退养资格驳回'
|
|
|
+ if (role.key === 'town') return a.type === '审批超时'
|
|
|
+ return true
|
|
|
+ })
|
|
|
const apiTodos = (wb.todos || []).map((t) => ({
|
|
|
id: String(t.id || ''),
|
|
|
type: String(t.type || '待办'),
|
|
|
@@ -743,6 +866,12 @@ export type PeoplePageParams = {
|
|
|
bizStatusIn?: string
|
|
|
/** 非本村视角隐藏他村 DRAFT */
|
|
|
hideDraftUnlessVillage?: boolean
|
|
|
+ /** 名单月份 yyyyMM,新增名单导出标题用 */
|
|
|
+ payMonth?: string
|
|
|
+ /** 享受起始年月 yyyyMM,镇经发月名单只收本月 */
|
|
|
+ enjoyStartMonth?: string
|
|
|
+ /** 村级导出不带银行卡、开户行 */
|
|
|
+ omitBank?: boolean
|
|
|
pageNum?: number
|
|
|
pageSize?: number
|
|
|
}
|
|
|
@@ -780,6 +909,7 @@ export async function fetchPeoplePage(
|
|
|
bizStatusIn: params?.bizStatusIn || undefined,
|
|
|
hideDraftUnlessVillage: params?.hideDraftUnlessVillage ? true : undefined,
|
|
|
payMonth: params?.batchNo || undefined,
|
|
|
+ enjoyStartMonth: params?.enjoyStartMonth || undefined,
|
|
|
}, { timeout: 90_000 })
|
|
|
const list = (res?.list || []).map((p) => mapPerson(p, params?.batchNo || currentPayMonth()))
|
|
|
return {
|
|
|
@@ -904,6 +1034,115 @@ export async function exportPersonnelImportFailRows(failRows: PersonnelImportFai
|
|
|
URL.revokeObjectURL(url)
|
|
|
}
|
|
|
|
|
|
+async function downloadPostBlob(
|
|
|
+ url: string,
|
|
|
+ body: Record<string, unknown>,
|
|
|
+ filename: string,
|
|
|
+) {
|
|
|
+ const res = await service.post(url, body, {
|
|
|
+ responseType: 'blob',
|
|
|
+ timeout: 180000,
|
|
|
+ })
|
|
|
+ const blob = res.data as Blob
|
|
|
+ if (blob && blob.type && blob.type.includes('application/json')) {
|
|
|
+ const text = await blob.text()
|
|
|
+ let msg = '导出失败'
|
|
|
+ try {
|
|
|
+ const j = JSON.parse(text) as { message?: string; msg?: string }
|
|
|
+ msg = j.message || j.msg || msg
|
|
|
+ } catch {
|
|
|
+ /* ignore */
|
|
|
+ }
|
|
|
+ throw new Error(msg)
|
|
|
+ }
|
|
|
+ const href = URL.createObjectURL(blob)
|
|
|
+ const a = document.createElement('a')
|
|
|
+ a.href = href
|
|
|
+ a.download = filename
|
|
|
+ document.body.appendChild(a)
|
|
|
+ a.click()
|
|
|
+ a.remove()
|
|
|
+ URL.revokeObjectURL(href)
|
|
|
+}
|
|
|
+
|
|
|
+async function peopleExportScope(params?: PeoplePageParams) {
|
|
|
+ const cfg = getAppConfig()
|
|
|
+ const role = cfg.roles.find((r) => r.key === cfg.currentRole)
|
|
|
+ const town = params?.town || role?.town
|
|
|
+ const village = params?.village || role?.village
|
|
|
+ const { townId, villageId } = await resolveScopeIds(town, village)
|
|
|
+ return {
|
|
|
+ townId: townId || undefined,
|
|
|
+ villageId: villageId || undefined,
|
|
|
+ name: params?.name || undefined,
|
|
|
+ idNumber: params?.idCard || undefined,
|
|
|
+ phoneNumber: params?.phone || undefined,
|
|
|
+ bizStatus: params?.bizStatus || undefined,
|
|
|
+ insuranceMatchResult: params?.insuranceMatchResult || undefined,
|
|
|
+ retireQualifyType:
|
|
|
+ params?.retireQualifyType && params.retireQualifyType !== 'UNSET'
|
|
|
+ ? params.retireQualifyType
|
|
|
+ : undefined,
|
|
|
+ source: params?.source || undefined,
|
|
|
+ bizStatusNotIn: params?.bizStatusNotIn || undefined,
|
|
|
+ bizStatusIn: params?.bizStatusIn || undefined,
|
|
|
+ hideDraftUnlessVillage: params?.hideDraftUnlessVillage ? true : undefined,
|
|
|
+ payMonth: params?.payMonth || undefined,
|
|
|
+ enjoyStartMonth: params?.enjoyStartMonth || params?.payMonth || undefined,
|
|
|
+ omitBank: params?.omitBank ? true : undefined,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export async function exportPersonnelRoster(params?: PeoplePageParams) {
|
|
|
+ const village = params?.village || getAppConfig().roles.find((r) => r.key === getAppConfig().currentRole)?.village || ''
|
|
|
+ await downloadPostBlob(
|
|
|
+ '/personnel/export-roster',
|
|
|
+ await peopleExportScope(params),
|
|
|
+ village ? `${village}人员列表.csv` : '本村人员列表.csv',
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export async function exportPersonnelLedger(params?: PeoplePageParams) {
|
|
|
+ await downloadPostBlob('/personnel/export-ledger', await peopleExportScope(params), '人员台账.csv')
|
|
|
+}
|
|
|
+
|
|
|
+export async function exportPersonnelNewMonth(params?: PeoplePageParams) {
|
|
|
+ const town = params?.town || getAppConfig().roles.find((r) => r.key === getAppConfig().currentRole)?.town || ''
|
|
|
+ await downloadPostBlob(
|
|
|
+ '/personnel/export-new-month',
|
|
|
+ await peopleExportScope(params),
|
|
|
+ `松江区${town}老年农民享受土地退养补助金新增名单.xlsx`,
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export type DistrictAdjustNotice = {
|
|
|
+ adjusted?: boolean
|
|
|
+ year?: string
|
|
|
+ adjustTime?: number
|
|
|
+ message?: string
|
|
|
+}
|
|
|
+
|
|
|
+export async function fetchDistrictAdjustNotice(): Promise<DistrictAdjustNotice> {
|
|
|
+ const raw = await get<DistrictAdjustNotice>('/amount-standards/adjust-notice')
|
|
|
+ return raw || { adjusted: false }
|
|
|
+}
|
|
|
+
|
|
|
+export async function submitDistrictYearlyAdjust(payload: {
|
|
|
+ monthly: number
|
|
|
+ funeral: number
|
|
|
+ effectiveMonth?: string
|
|
|
+ reason?: string
|
|
|
+}) {
|
|
|
+ return post<Record<string, unknown>>('/amount-standards/district-yearly-adjust', {
|
|
|
+ monthlyAmount: payload.monthly,
|
|
|
+ funeralAmount: payload.funeral,
|
|
|
+ effectiveMonth: payload.effectiveMonth,
|
|
|
+ adjustReason: payload.reason,
|
|
|
+ operator: operatorOf(),
|
|
|
+ districtWide: true,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
export async function fetchPersonnelImportHistory(limit = 50): Promise<PersonnelImportHistoryItem[]> {
|
|
|
const list = await get<PersonnelImportHistoryItem[]>('/personnel/import/history', { limit })
|
|
|
return Array.isArray(list) ? list : []
|
|
|
@@ -920,12 +1159,17 @@ export async function fetchAlertDetail(alertId: string): Promise<AlertItem | nul
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export async function fetchPersonDetail(personnelId: string): Promise<Person | null> {
|
|
|
+export async function fetchPersonDetail(
|
|
|
+ personnelId: string,
|
|
|
+ config?: { silent?: boolean },
|
|
|
+): Promise<Person | null> {
|
|
|
if (!personnelId) return null
|
|
|
await ensureTowns()
|
|
|
try {
|
|
|
const raw = await get<Record<string, unknown>>(
|
|
|
`/personnel/${encodeURIComponent(personnelId)}`,
|
|
|
+ undefined,
|
|
|
+ { silent: config?.silent },
|
|
|
)
|
|
|
return raw ? mapPerson(raw) : null
|
|
|
} catch {
|
|
|
@@ -957,9 +1201,28 @@ export async function savePerson(payload: Partial<Person>) {
|
|
|
bankType: blank(payload.bankType),
|
|
|
bankBranchCode: blank(payload.bankBranchCode),
|
|
|
socialInsuranceCode: blank(payload.socialInsuranceCode),
|
|
|
+ localOrRemote: blank(payload.localOrRemote),
|
|
|
+ retireQualifyType: qualifyTypeCode(payload.retireQualifyType),
|
|
|
phoneNumber: blank(payload.phoneNumber),
|
|
|
address: blank(payload.address),
|
|
|
+ householdAddress: blank(payload.householdAddress),
|
|
|
householdType: blank(payload.householdType),
|
|
|
+ personType: blank(payload.personType),
|
|
|
+ urbanMedicareConfirm: blank(payload.urbanMedicareConfirm),
|
|
|
+ householdDistrict: blank(payload.householdDistrict),
|
|
|
+ householdTown: blank(payload.householdTown),
|
|
|
+ householdVillage: blank(payload.householdVillage),
|
|
|
+ contactName: blank(payload.contactName),
|
|
|
+ contactPhone: blank(payload.contactPhone),
|
|
|
+ insuranceJoinDate: blank(payload.insuranceJoinDate),
|
|
|
+ personMark: blank(payload.personMark),
|
|
|
+ benefitApplyDate: blank(payload.benefitApplyDate),
|
|
|
+ heirIdType: isEdit ? String(payload.heirIdType ?? '').trim() : blank(payload.heirIdType),
|
|
|
+ heirBankName: isEdit ? String(payload.heirBankName ?? '').trim() : blank(payload.heirBankName),
|
|
|
+ heirHouseholdAddress: isEdit
|
|
|
+ ? String(payload.heirHouseholdAddress ?? '').trim()
|
|
|
+ : blank(payload.heirHouseholdAddress),
|
|
|
+ heirAddress: isEdit ? String(payload.heirAddress ?? '').trim() : blank(payload.heirAddress),
|
|
|
villageCode: blank(payload.villageCode),
|
|
|
entrustPlanCode: blank(payload.entrustPlanCode),
|
|
|
entrustPlanName: blank(payload.entrustPlanName),
|
|
|
@@ -974,6 +1237,7 @@ export async function savePerson(payload: Partial<Person>) {
|
|
|
nextPayStartMonth: monthToPayMonth(payload.nextPayStartMonth) || undefined,
|
|
|
payeeName: blank(payload.payeeName) || blank(payload.name),
|
|
|
deathDate: isEdit ? String(payload.deathDate ?? '').trim() : blank(payload.deathDate),
|
|
|
+ deathStatus: isEdit ? String(payload.deathStatus ?? '').trim() : blank(payload.deathStatus),
|
|
|
heirName: isEdit ? String(payload.heirName ?? '').trim() : blank(payload.heirName),
|
|
|
heirRelation: isEdit ? String(payload.heirRelation ?? '').trim() : blank(payload.heirRelation),
|
|
|
heirIdNumber: isEdit ? String(payload.heirIdNumber ?? '').trim() : blank(payload.heirIdNumber),
|
|
|
@@ -1064,6 +1328,7 @@ export async function reviewRetireQualify(payload: {
|
|
|
fileUrls: payload.fileUrls,
|
|
|
operator: operatorOf(),
|
|
|
})
|
|
|
+ clearGetCache()
|
|
|
return { ok: true }
|
|
|
}
|
|
|
|
|
|
@@ -1082,6 +1347,7 @@ export async function reviewRetireQualifyBatch(payload: {
|
|
|
fileUrls: payload.fileUrls,
|
|
|
operator: operatorOf(),
|
|
|
}, { timeout: 180_000 })
|
|
|
+ clearGetCache()
|
|
|
return {
|
|
|
ok: res?.ok !== false,
|
|
|
message: String(res?.message || ''),
|
|
|
@@ -1138,6 +1404,19 @@ export async function updateBatchMembers(batchId: string, personnelIds: string[]
|
|
|
return { ok: true }
|
|
|
}
|
|
|
|
|
|
+/** 测算锁定前向发放批增量加人(镇统计批自动改挂全区父批) */
|
|
|
+export async function addBatchMembers(batchId: string, personnelIds: string[]) {
|
|
|
+ const res = await post<Record<string, unknown>>(
|
|
|
+ `/payments/batches/${encodeURIComponent(batchId)}/add-members`,
|
|
|
+ {
|
|
|
+ personnelIds,
|
|
|
+ operator: operatorOf(),
|
|
|
+ },
|
|
|
+ { timeout: 60000 },
|
|
|
+ )
|
|
|
+ return { ok: true, added: Number(res?.added || 0), message: String(res?.message || '') }
|
|
|
+}
|
|
|
+
|
|
|
/** 批次已纳入人员 ID(发放明细) */
|
|
|
export async function fetchBatchMemberIds(batchId: string): Promise<string[]> {
|
|
|
if (!batchId) return []
|
|
|
@@ -1385,19 +1664,23 @@ export async function fetchBatchMemberPeoplePage(
|
|
|
const { townId, villageId } = await resolveScopeIds(town, villageKey)
|
|
|
const queryBatchId = opts?.parentBatchId || batchId
|
|
|
|
|
|
- const detailsRes = await post<PageResult<Record<string, unknown>>>('/payments/details/page', {
|
|
|
- pageNum,
|
|
|
- pageSize,
|
|
|
- batchId: queryBatchId,
|
|
|
- townId: townId || undefined,
|
|
|
- villageId: villageId || undefined,
|
|
|
- name: opts?.name?.trim() || undefined,
|
|
|
- idNumber: opts?.idCard?.trim() || undefined,
|
|
|
- sortBy: opts?.sortBy || undefined,
|
|
|
- sortOrder: opts?.sortOrder || undefined,
|
|
|
- onlyFuneral: opts?.onlyFuneral || undefined,
|
|
|
- onlySupplement: opts?.onlySupplement || undefined,
|
|
|
- })
|
|
|
+ const detailsRes = await post<PageResult<Record<string, unknown>>>(
|
|
|
+ '/payments/details/page',
|
|
|
+ {
|
|
|
+ pageNum,
|
|
|
+ pageSize,
|
|
|
+ batchId: queryBatchId,
|
|
|
+ townId: townId || undefined,
|
|
|
+ villageId: villageId || undefined,
|
|
|
+ name: opts?.name?.trim() || undefined,
|
|
|
+ idNumber: opts?.idCard?.trim() || undefined,
|
|
|
+ sortBy: opts?.sortBy || undefined,
|
|
|
+ sortOrder: opts?.sortOrder || undefined,
|
|
|
+ onlyFuneral: opts?.onlyFuneral || undefined,
|
|
|
+ onlySupplement: opts?.onlySupplement || undefined,
|
|
|
+ },
|
|
|
+ { timeout: 60_000 },
|
|
|
+ )
|
|
|
const details = detailsRes?.list || []
|
|
|
const batchNo = opts?.batchNo || currentPayMonth()
|
|
|
const result: BatchMemberPerson[] = []
|
|
|
@@ -1848,6 +2131,54 @@ function toTownApproval(vb: VillageBatch, status: string): Approval {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+async function fillQualifyInsurance(list: Approval[]) {
|
|
|
+ const ids = list
|
|
|
+ .filter((r) => {
|
|
|
+ const t = String(r.insuranceStatus || '')
|
|
|
+ return r.personnelId && (t === '' || t === '未匹配')
|
|
|
+ })
|
|
|
+ .map((r) => String(r.personnelId))
|
|
|
+ if (!ids.length) return
|
|
|
+ const rows = await post<Array<{ personnelId?: string; matchResult?: string; reason?: string }>>(
|
|
|
+ '/personnel/qualify-insurance-match',
|
|
|
+ { personnelIds: ids },
|
|
|
+ { silent: true },
|
|
|
+ ).catch(() => null)
|
|
|
+ const byId = new Map((rows || []).map((x) => [String(x.personnelId || ''), x]))
|
|
|
+ for (const row of list) {
|
|
|
+ const hit = byId.get(String(row.personnelId || ''))
|
|
|
+ if (!hit?.reason) continue
|
|
|
+ row.insuranceStatus = hit.reason
|
|
|
+ row.insuranceReason = hit.reason === '匹配通过' ? '' : hit.reason
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function attachQualifySubmitTime(list: Approval[], townId?: string) {
|
|
|
+ if (!list.length) return
|
|
|
+ const logs = await post<PageResult<Record<string, unknown>>>(
|
|
|
+ '/process-logs/page',
|
|
|
+ {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 500,
|
|
|
+ townId: townId || undefined,
|
|
|
+ bizType: 'RETIRE_QUALIFY',
|
|
|
+ action: 'QUALIFY_SUBMIT',
|
|
|
+ },
|
|
|
+ ).catch(() => null)
|
|
|
+ const times = new Map<string, string>()
|
|
|
+ for (const raw of logs?.list || []) {
|
|
|
+ const pid = String(raw.personnelId || '')
|
|
|
+ const when = mapProcessLog(raw).operateTime
|
|
|
+ if (!pid || !when) continue
|
|
|
+ const prev = times.get(pid)
|
|
|
+ if (!prev || when > prev) times.set(pid, when)
|
|
|
+ }
|
|
|
+ for (const row of list) {
|
|
|
+ const pid = String(row.personnelId || '')
|
|
|
+ row.addedAt = times.get(pid) || row.createdAt || ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
function toQualifyApproval(p: Person, status: string): Approval {
|
|
|
return {
|
|
|
id: approvalIdQualify(String(p.id)),
|
|
|
@@ -1864,6 +2195,15 @@ function toQualifyApproval(p: Person, status: string): Approval {
|
|
|
personName: p.name,
|
|
|
personnelId: String(p.id),
|
|
|
idCard: p.idCard,
|
|
|
+ householdType: p.householdType,
|
|
|
+ startMonth: p.startMonth,
|
|
|
+ standard: p.standard,
|
|
|
+ remark: p.remark,
|
|
|
+ retireQualifyType: p.retireQualifyType,
|
|
|
+ insuranceStatus: p.insuranceReason || p.insuranceStatus || '未匹配',
|
|
|
+ insuranceReason: p.insuranceReason,
|
|
|
+ deathWarning: p.deathWarning,
|
|
|
+ deathStatus: p.deathStatus,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1895,6 +2235,7 @@ export async function fetchApprovals(params?: {
|
|
|
pageNum?: number
|
|
|
pageSize?: number
|
|
|
status?: string
|
|
|
+ enjoyStartMonth?: string
|
|
|
}): Promise<{ list: Approval[]; total: number; pageNum: number; pageSize: number }> {
|
|
|
await ensureTowns()
|
|
|
const { townId, villageId } = await resolveScopeIds(params?.town, params?.village)
|
|
|
@@ -1911,11 +2252,15 @@ export async function fetchApprovals(params?: {
|
|
|
town: params?.town,
|
|
|
village: params?.village,
|
|
|
bizStatus: 'PENDING_TOWN_APPROVE',
|
|
|
+ enjoyStartMonth: params?.enjoyStartMonth,
|
|
|
pageNum,
|
|
|
pageSize,
|
|
|
})
|
|
|
+ const list = people.list.map((p) => toQualifyApproval(p, '待审批'))
|
|
|
+ await attachQualifySubmitTime(list, townId)
|
|
|
+ await fillQualifyInsurance(list)
|
|
|
return {
|
|
|
- list: people.list.map((p) => toQualifyApproval(p, '待审批')),
|
|
|
+ list,
|
|
|
total: people.total,
|
|
|
pageNum,
|
|
|
pageSize,
|
|
|
@@ -1928,16 +2273,17 @@ export async function fetchApprovals(params?: {
|
|
|
: status === '已驳回'
|
|
|
? 'QUALIFY_REJECT'
|
|
|
: 'QUALIFY_PASS,QUALIFY_REJECT'
|
|
|
+ const rosterMonth = String(params?.enjoyStartMonth || '').replace(/-/g, '')
|
|
|
const logs = await post<PageResult<Record<string, unknown>>>('/process-logs/page', {
|
|
|
- pageNum,
|
|
|
- pageSize,
|
|
|
+ pageNum: rosterMonth ? 1 : pageNum,
|
|
|
+ pageSize: rosterMonth ? 200 : pageSize,
|
|
|
townId: townId || undefined,
|
|
|
villageId: villageId || undefined,
|
|
|
bizType: 'RETIRE_QUALIFY',
|
|
|
action,
|
|
|
})
|
|
|
const rows = logs?.list || []
|
|
|
- const list: Approval[] = []
|
|
|
+ const built: Approval[] = []
|
|
|
await Promise.all(
|
|
|
rows.map(async (log) => {
|
|
|
const pid = String(log.personnelId || '')
|
|
|
@@ -1953,9 +2299,27 @@ export async function fetchApprovals(params?: {
|
|
|
)
|
|
|
const mapped = mapProcessLog(log)
|
|
|
if (mapped.operateTime) row.createdAt = mapped.operateTime
|
|
|
- list.push(row)
|
|
|
+ built.push(row)
|
|
|
}),
|
|
|
)
|
|
|
+ const seen = new Set<string>()
|
|
|
+ const list = built.filter((row) => {
|
|
|
+ if (rosterMonth) {
|
|
|
+ const start = String(row.startMonth || '').replace(/-/g, '')
|
|
|
+ if (!start.startsWith(rosterMonth)) return false
|
|
|
+ }
|
|
|
+ const key = String(row.personnelId || row.id || '')
|
|
|
+ if (!key || seen.has(key)) return false
|
|
|
+ seen.add(key)
|
|
|
+ return true
|
|
|
+ })
|
|
|
+ if (rosterMonth) {
|
|
|
+ const from = (pageNum - 1) * pageSize
|
|
|
+ const pageList = list.slice(from, from + pageSize)
|
|
|
+ await attachQualifySubmitTime(pageList, townId)
|
|
|
+ return { list: pageList, total: list.length, pageNum, pageSize }
|
|
|
+ }
|
|
|
+ await attachQualifySubmitTime(list, townId)
|
|
|
return { list, total: Number(logs?.total || list.length), pageNum, pageSize }
|
|
|
}
|
|
|
|
|
|
@@ -2205,6 +2569,7 @@ export async function decideApprovals(payload: {
|
|
|
retireQualifyType: payload.retireQualifyType,
|
|
|
})
|
|
|
}
|
|
|
+ clearGetCache()
|
|
|
return { ok: true }
|
|
|
}
|
|
|
|
|
|
@@ -2225,6 +2590,7 @@ export async function decideApproval(payload: {
|
|
|
opinion: payload.opinion,
|
|
|
fileUrls: payload.fileUrls,
|
|
|
})
|
|
|
+ clearGetCache()
|
|
|
return { ok: true }
|
|
|
}
|
|
|
const body = {
|
|
|
@@ -2253,12 +2619,9 @@ export async function fetchStatusBatches(): Promise<VillageBatch[]> {
|
|
|
|
|
|
const standardIdMap = new Map<number, string>()
|
|
|
|
|
|
-export async function fetchStandards(town?: string): Promise<SubsidyStandard[]> {
|
|
|
+export async function fetchStandards(_town?: string): Promise<SubsidyStandard[]> {
|
|
|
await ensureTowns()
|
|
|
- const townId = town ? townIdOf(town) : undefined
|
|
|
- // 后端返回 PageResult,不是裸数组
|
|
|
const page = await get<PageResult<Record<string, unknown>>>('/amount-standards', {
|
|
|
- townId: townId || undefined,
|
|
|
pageNum: 1,
|
|
|
pageSize: 200,
|
|
|
})
|
|
|
@@ -2272,15 +2635,11 @@ export async function fetchStandards(town?: string): Promise<SubsidyStandard[]>
|
|
|
}
|
|
|
|
|
|
/** 本镇当前有效丧葬补贴标准(元);优先 /amount-standards/current,失败则回退标准列表 */
|
|
|
-export async function fetchCurrentFuneralAmount(town: string): Promise<number | null> {
|
|
|
- if (!town) return null
|
|
|
- await ensureTowns()
|
|
|
- const townId = townIdOf(town)
|
|
|
- if (!townId) return null
|
|
|
+export async function fetchCurrentFuneralAmount(_town?: string): Promise<number | null> {
|
|
|
try {
|
|
|
const raw = await get<Record<string, unknown>>(
|
|
|
'/amount-standards/current',
|
|
|
- { townId, standardType: 'FUNERAL' },
|
|
|
+ { standardType: 'FUNERAL' },
|
|
|
{ silent: true },
|
|
|
)
|
|
|
const amt = Number(raw?.funeralAmount ?? raw?.amount ?? 0)
|
|
|
@@ -2289,7 +2648,7 @@ export async function fetchCurrentFuneralAmount(town: string): Promise<number |
|
|
|
/* 回退列表 */
|
|
|
}
|
|
|
try {
|
|
|
- const list = await fetchStandards(town)
|
|
|
+ const list = await fetchStandards()
|
|
|
const cur = list.find((s) => s.current && s.funeral > 0) || list.find((s) => s.funeral > 0)
|
|
|
return cur && cur.funeral > 0 ? cur.funeral : null
|
|
|
} catch {
|
|
|
@@ -2319,17 +2678,14 @@ export async function createStandard(payload: {
|
|
|
date: string
|
|
|
reason: string
|
|
|
}) {
|
|
|
- const townId = townIdOf(payload.town)
|
|
|
- if (!townId) return { ok: false as const, message: '未识别街镇' }
|
|
|
let effectiveTime: number
|
|
|
try {
|
|
|
effectiveTime = dateToEpochMs(payload.date)
|
|
|
} catch {
|
|
|
return { ok: false as const, message: '请选择有效的生效日期' }
|
|
|
}
|
|
|
- // 新增标准只落库,不走 yearly-adjust(那会生成调标补发批)
|
|
|
const saved = await post<Record<string, unknown>>('/amount-standards', {
|
|
|
- townId,
|
|
|
+ townId: 'T_DISTRICT',
|
|
|
standardType: 'YEARLY_ADJUST',
|
|
|
amount: payload.monthly,
|
|
|
funeralAmount: payload.funeral,
|
|
|
@@ -2361,11 +2717,9 @@ export async function updateStandard(payload: {
|
|
|
} catch {
|
|
|
return { ok: false as const, message: '请选择有效的生效日期' }
|
|
|
}
|
|
|
- const townId = payload.town ? townIdOf(payload.town) : ''
|
|
|
- if (!townId) return { ok: false as const, message: '未识别街镇' }
|
|
|
const saved = await post<Record<string, unknown>>('/amount-standards', {
|
|
|
id: realId,
|
|
|
- townId,
|
|
|
+ townId: 'T_DISTRICT',
|
|
|
standardType: 'YEARLY_ADJUST',
|
|
|
amount: payload.monthly,
|
|
|
funeralAmount: payload.funeral,
|
|
|
@@ -2580,6 +2934,12 @@ export async function fetchDeaths(params?: {
|
|
|
heirIdCard: s.heirIdNumber ? String(s.heirIdNumber) : '',
|
|
|
heirBankCard: s.heirBankCard ? String(s.heirBankCard) : '',
|
|
|
heirContact: s.heirPhone ? String(s.heirPhone) : '',
|
|
|
+ heirRelation: s.heirRelation ? String(s.heirRelation) : '',
|
|
|
+ heirIdType: s.heirIdType ? String(s.heirIdType) : '',
|
|
|
+ heirBankName: s.heirBankName ? String(s.heirBankName) : '',
|
|
|
+ heirHouseholdAddress: s.heirHouseholdAddress ? String(s.heirHouseholdAddress) : '',
|
|
|
+ heirAddress: s.heirAddress ? String(s.heirAddress) : '',
|
|
|
+ cancelApplyDate: s.cancelApplyDate ? String(s.cancelApplyDate).slice(0, 10) : '',
|
|
|
source: '社区事务受理中心手工录入',
|
|
|
status: listStatus,
|
|
|
createdAt: fmtTimeSafe(s.createTime as number),
|
|
|
@@ -2679,6 +3039,10 @@ export async function createDeathRecord(payload: {
|
|
|
heirIdCard?: string
|
|
|
heirBankCard?: string
|
|
|
heirContact?: string
|
|
|
+ heirIdType?: string
|
|
|
+ heirBankName?: string
|
|
|
+ heirHouseholdAddress?: string
|
|
|
+ heirAddress?: string
|
|
|
fileUrls?: string
|
|
|
}) {
|
|
|
let personnelId = payload.personId ? personIdStr(payload.personId) : ''
|
|
|
@@ -2723,6 +3087,11 @@ export async function createDeathRecord(payload: {
|
|
|
heirIdNumber: payload.heirIdCard || undefined,
|
|
|
heirBankCard: payload.heirBankCard || undefined,
|
|
|
heirPhone: payload.heirContact || undefined,
|
|
|
+ heirIdType: payload.heirIdType || undefined,
|
|
|
+ heirBankName: payload.heirBankName || undefined,
|
|
|
+ heirHouseholdAddress: payload.heirHouseholdAddress || undefined,
|
|
|
+ heirAddress: payload.heirAddress || undefined,
|
|
|
+ hasHeir: payload.heirName ? '是' : undefined,
|
|
|
source: '死亡登记',
|
|
|
expectedUpdateTime: Number.isFinite(ut) && ut > 0 ? ut : undefined,
|
|
|
}).catch(() => undefined)
|
|
|
@@ -2736,6 +3105,11 @@ export async function createDeathRecord(payload: {
|
|
|
heirIdNumber: payload.heirIdCard,
|
|
|
heirBankCard: payload.heirBankCard,
|
|
|
heirPhone: payload.heirContact,
|
|
|
+ heirRelation: payload.heirRelation,
|
|
|
+ heirIdType: payload.heirIdType,
|
|
|
+ heirBankName: payload.heirBankName,
|
|
|
+ heirHouseholdAddress: payload.heirHouseholdAddress,
|
|
|
+ heirAddress: payload.heirAddress,
|
|
|
fileUrls: payload.fileUrls,
|
|
|
townId: detail?.townId || townId,
|
|
|
operator: operatorOf(),
|
|
|
@@ -2756,6 +3130,8 @@ export async function createSpecialBiz(payload: {
|
|
|
reason: string
|
|
|
amount: number
|
|
|
heirName?: string
|
|
|
+ sentenceStart?: string
|
|
|
+ sentenceEnd?: string
|
|
|
fileUrls?: string
|
|
|
/** 多发退款方案:DEDUCT_NEXT | RETURN_FISCAL */
|
|
|
refundMethod?: 'DEDUCT_NEXT' | 'RETURN_FISCAL'
|
|
|
@@ -2793,6 +3169,8 @@ export async function createSpecialBiz(payload: {
|
|
|
amount: payload.amount,
|
|
|
townId: townId || undefined,
|
|
|
heirName: payload.heirName,
|
|
|
+ sentenceStart: payload.sentenceStart,
|
|
|
+ sentenceEnd: payload.sentenceEnd,
|
|
|
fileUrls: payload.fileUrls,
|
|
|
refundMethod: bizType === 'REFUND' ? payload.refundMethod || 'DEDUCT_NEXT' : undefined,
|
|
|
operator: operatorOf(),
|
|
|
@@ -3753,6 +4131,10 @@ export async function archiveToNextMonth(id?: string) {
|
|
|
export async function fetchAlertsPage(params?: {
|
|
|
town?: string
|
|
|
village?: string
|
|
|
+ name?: string
|
|
|
+ type?: string
|
|
|
+ status?: string
|
|
|
+ level?: string
|
|
|
pageNum?: number
|
|
|
pageSize?: number
|
|
|
}): Promise<{ list: AlertItem[]; total: number; pageNum: number; pageSize: number }> {
|
|
|
@@ -3765,6 +4147,10 @@ export async function fetchAlertsPage(params?: {
|
|
|
pageSize,
|
|
|
townId: townId || undefined,
|
|
|
villageId: villageId || undefined,
|
|
|
+ personnelName: params?.name || undefined,
|
|
|
+ type: params?.type || undefined,
|
|
|
+ handleStatus: params?.status || undefined,
|
|
|
+ level: params?.level || undefined,
|
|
|
})
|
|
|
return {
|
|
|
list: (res?.list || []).map(mapAlert),
|