biz.ts 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287
  1. import { ElMessage } from 'element-plus'
  2. import { getAppConfig } from '@/utils/appConfig'
  3. import { DISK_RETURN_FAIL_REASONS } from '@/constants/diskReturn'
  4. import type {
  5. BatchMemberPerson,
  6. HandleAlertPayload,
  7. OpLog,
  8. Person,
  9. VillageBatch,
  10. VillageMonthlyBatch,
  11. } from '@/types/biz'
  12. import { buildDashboardByRole } from '@/utils/buildDashboard'
  13. import * as real from './sjnmtybt/realBiz'
  14. import {
  15. mockAccounts,
  16. mockAlerts,
  17. mockApprovals,
  18. mockDeaths,
  19. mockDisks,
  20. mockDiskFailPeople,
  21. mockLogs,
  22. mockMonthlyBatches,
  23. mockPeople,
  24. mockSpecials,
  25. mockStandards,
  26. mockVillageBatches,
  27. mockAdjustReissueRoster,
  28. nowStr,
  29. getVillagesByTown,
  30. getStandardByTown,
  31. } from './mock/data'
  32. /** 是否走 mock:改 public/config/config.js 的 useMock 即可切换 */
  33. function useMock() {
  34. return getAppConfig().useMock
  35. }
  36. function delay<T>(data: T, ms = 200): Promise<T> {
  37. return new Promise((resolve) => setTimeout(() => resolve(data), ms))
  38. }
  39. function mockPaged<T>(list: T[], pageNum = 1, pageSize = 10) {
  40. const start = (pageNum - 1) * pageSize
  41. return delay({
  42. list: list.slice(start, start + pageSize),
  43. total: list.length,
  44. pageNum,
  45. pageSize,
  46. })
  47. }
  48. /* ========== 工作台 ========== */
  49. export function fetchDashboard(params: {
  50. roleKey: string
  51. currentMonth: string
  52. }) {
  53. if (useMock()) {
  54. const role =
  55. getAppConfig().roles.find((r) => r.key === params.roleKey) ||
  56. getAppConfig().roles[0]
  57. return delay(buildDashboardByRole(role, params.currentMonth))
  58. }
  59. return real.fetchDashboard(params)
  60. }
  61. /** 工作台待办已读 */
  62. export function markWorkbenchTodoRead(todoKey: string) {
  63. if (useMock()) return delay(undefined)
  64. return real.markWorkbenchTodoRead(todoKey)
  65. }
  66. /* ========== 基础信息 / 人员新增录入 ========== */
  67. export function fetchVillageBatches() {
  68. if (useMock()) return delay([...mockVillageBatches])
  69. return real.fetchVillageBatches()
  70. }
  71. export function submitRetireQualify(
  72. personnelId: string,
  73. payload?: { opinion?: string; fileUrls?: string },
  74. config?: { silent?: boolean },
  75. ) {
  76. if (useMock()) return delay({ ok: true })
  77. return real.submitRetireQualify(personnelId, payload, config)
  78. }
  79. export function submitRetireQualifyBatch(payload: {
  80. personnelIds: string[]
  81. opinion?: string
  82. fileUrls?: string
  83. }) {
  84. if (useMock()) return delay({ ok: true, successCount: payload.personnelIds.length, pendingCount: 0, failCount: 0, message: '' })
  85. return real.submitRetireQualifyBatch(payload)
  86. }
  87. export function generateDistrictMonthBatch(
  88. payMonth?: string,
  89. matchInsurance = true,
  90. simulateFirstDay = false,
  91. ) {
  92. if (useMock()) return delay({ ok: true })
  93. return real.generateDistrictMonthBatch(payMonth, matchInsurance, simulateFirstDay)
  94. }
  95. export function fetchPendingSpecialCount(town?: string) {
  96. if (useMock()) return delay(0)
  97. return real.fetchPendingSpecialCount(town)
  98. }
  99. export function fetchPeople(params?: {
  100. batchNo?: string
  101. town?: string
  102. village?: string
  103. }) {
  104. if (useMock()) {
  105. let list = [...mockPeople]
  106. if (params?.batchNo) list = list.filter((p) => p.batchNo === params.batchNo)
  107. if (params?.town) list = list.filter((p) => p.town === params.town)
  108. if (params?.village) list = list.filter((p) => p.village === params.village)
  109. return delay(list)
  110. }
  111. return real.fetchPeople(params)
  112. }
  113. export function fetchPeoplePage(params?: real.PeoplePageParams) {
  114. if (useMock()) {
  115. let list = [...mockPeople]
  116. if (params?.town) list = list.filter((p) => p.town === params.town)
  117. if (params?.village) list = list.filter((p) => p.village === params.village)
  118. if (params?.name) list = list.filter((p) => p.name?.includes(params.name!))
  119. if (params?.idCard) list = list.filter((p) => p.idCard?.includes(params.idCard!))
  120. if (params?.phone) {
  121. list = list.filter((p) => String((p as Person).phoneNumber || '').includes(params.phone!))
  122. }
  123. if (params?.source) list = list.filter((p) => String(p.source || '').includes(params.source!))
  124. if (params?.bizStatus) {
  125. const code = String(params.bizStatus).toUpperCase()
  126. list = list.filter(
  127. (p) =>
  128. String((p as Person).bizStatus || '').toUpperCase() === code ||
  129. (code === 'NORMAL' && p.status === '正常在库') ||
  130. (code === 'PAUSED' && p.status === '暂停') ||
  131. (code === 'STOPPED' && p.status === '停止') ||
  132. (code === 'FUNERAL' && String(p.status).includes('死亡')),
  133. )
  134. }
  135. if (params?.insuranceMatchResult) {
  136. const code = String(params.insuranceMatchResult).toUpperCase()
  137. list = list.filter(
  138. (p) => String((p as Person).insuranceMatchResult || '').toUpperCase() === code,
  139. )
  140. }
  141. if (params?.retireQualifyType) {
  142. const code = String(params.retireQualifyType).toUpperCase()
  143. list = list.filter((p) => {
  144. const t = String((p as Person).retireQualifyType || '').toUpperCase()
  145. if (code === 'UNSET') return !t || t === 'UNSET' || t === 'NONE' || t === 'CLEARED'
  146. if (code === 'WITH_LAND') return t === 'WITH_LAND' || (p as Person).retireQualifyType === '有土地退养'
  147. if (code === 'WITHOUT_LAND') return t === 'WITHOUT_LAND' || (p as Person).retireQualifyType === '无土地有资格'
  148. if (code === 'INELIGIBLE') return t === 'INELIGIBLE' || (p as Person).retireQualifyType === '无资格'
  149. return t === code
  150. })
  151. }
  152. const pageNum = params?.pageNum || 1
  153. const pageSize = params?.pageSize || 10
  154. const start = (pageNum - 1) * pageSize
  155. return delay({
  156. list: list.slice(start, start + pageSize),
  157. total: list.length,
  158. pageNum,
  159. pageSize,
  160. })
  161. }
  162. return real.fetchPeoplePage(params)
  163. }
  164. export function downloadPersonnelImportTemplate(town?: string, village?: string) {
  165. if (useMock()) {
  166. ElMessage.info('模拟模式:请切换联调后下载正式模板')
  167. return delay(undefined)
  168. }
  169. return real.downloadPersonnelImportTemplate(town, village)
  170. }
  171. export function importPersonnelExcel(file: File, town?: string, village?: string) {
  172. if (useMock()) {
  173. ElMessage.success('模拟模式:已假装导入成功')
  174. return delay({ successCount: 0, failCount: 0, errors: [], failRows: [] })
  175. }
  176. return real.importPersonnelExcel(file, town, village)
  177. }
  178. export function exportPersonnelImportFailRows(failRows: real.PersonnelImportFailRow[]) {
  179. if (useMock()) {
  180. ElMessage.info('模拟模式:请切换联调后导出失败行')
  181. return delay(undefined)
  182. }
  183. return real.exportPersonnelImportFailRows(failRows)
  184. }
  185. export function fetchPersonnelImportHistory(limit = 50) {
  186. if (useMock()) {
  187. return delay([] as real.PersonnelImportHistoryItem[])
  188. }
  189. return real.fetchPersonnelImportHistory(limit)
  190. }
  191. export function fetchAlertDetail(alertId: string) {
  192. if (useMock()) {
  193. const hit = mockAlerts.find((a) => String(a.id) === String(alertId))
  194. return delay(
  195. hit
  196. ? {
  197. ...hit,
  198. lifecycle: [
  199. {
  200. actionLabel: '产生告警',
  201. status: '待处理',
  202. operator: '系统',
  203. result: hit.content,
  204. operateTime: hit.createdAt,
  205. },
  206. ...(hit.status === '已关闭'
  207. ? [
  208. {
  209. actionLabel: '关闭告警',
  210. status: '已关闭',
  211. operator: '业务部门',
  212. result: hit.result || '已核实并处理',
  213. operateTime: hit.createdAt,
  214. },
  215. ]
  216. : []),
  217. ],
  218. }
  219. : null,
  220. )
  221. }
  222. return real.fetchAlertDetail(alertId)
  223. }
  224. /** 确保本村本月批次;草稿/退回时纠正误标「待镇初审」人员 */
  225. export function ensureVillageMonthBatch(town?: string, village?: string, payMonth?: string) {
  226. if (useMock()) return delay(undefined)
  227. return real.ensureVillageMonthBatch(town, village, payMonth)
  228. }
  229. export function fetchPersonDetail(personnelId: string) {
  230. if (useMock()) {
  231. const hit = mockPeople.find((p) => String(p.id) === String(personnelId))
  232. return delay(hit ? { ...hit } : null)
  233. }
  234. return real.fetchPersonDetail(personnelId)
  235. }
  236. export function savePerson(payload: Partial<Person>) {
  237. if (useMock()) {
  238. const month = getAppConfig().currentMonth
  239. if (payload.id != null && payload.id !== '') {
  240. const hit = mockPeople.find((p) => String(p.id) === String(payload.id))
  241. if (hit) {
  242. Object.assign(hit, {
  243. ...payload,
  244. name: payload.name ?? hit.name,
  245. idCard: payload.idCard ?? hit.idCard,
  246. town: payload.town ?? hit.town,
  247. village: payload.village ?? hit.village,
  248. bankCard: payload.bankCard ?? hit.bankCard,
  249. bankName: payload.bankName ?? hit.bankName,
  250. socialInsuranceCode: payload.socialInsuranceCode ?? hit.socialInsuranceCode,
  251. standard: payload.standard ?? hit.standard,
  252. startMonth: payload.startMonth ?? hit.startMonth,
  253. remark: payload.remark ?? hit.remark,
  254. source: payload.source ?? hit.source,
  255. })
  256. return delay({ ok: true })
  257. }
  258. }
  259. const id = Math.max(0, ...mockPeople.map((p) => Number(p.id) || 0)) + 1
  260. mockPeople.push({
  261. id,
  262. batchNo: month.replace('-', ''),
  263. name: payload.name || '',
  264. idCard: payload.idCard || '',
  265. town: payload.town || '叶榭镇',
  266. village: payload.village || '叶榭村',
  267. bankCard: payload.bankCard || '',
  268. bankName: payload.bankName || '',
  269. socialInsuranceCode: payload.socialInsuranceCode || '',
  270. standard: payload.standard ?? 1280,
  271. startMonth: payload.startMonth || month,
  272. status: '草稿',
  273. source: '村合作社录入',
  274. insuranceStatus: '未匹配',
  275. remark: payload.remark,
  276. })
  277. return delay({ ok: true })
  278. }
  279. return real.savePerson(payload)
  280. }
  281. /** 更新批次纳入人员 */
  282. export function updateBatchMembers(batchId: string, personnelIds: string[]) {
  283. if (useMock()) {
  284. const batch = mockVillageBatches.find((b) => b.id === batchId)
  285. if (batch) {
  286. batch.peopleCount = personnelIds.length
  287. batch.totalAmount = personnelIds.length * 1280
  288. }
  289. return delay({ ok: true })
  290. }
  291. return real.updateBatchMembers(batchId, personnelIds)
  292. }
  293. /** 批次已纳入人员 ID */
  294. export function fetchBatchMemberIds(batchId: string) {
  295. if (useMock()) {
  296. const batch = mockVillageBatches.find((b) => b.id === batchId)
  297. if (!batch) return delay([] as string[])
  298. const ids = mockPeople
  299. .filter(
  300. (p) =>
  301. p.town === batch.town &&
  302. p.village === batch.village &&
  303. p.batchNo === batch.batchNo &&
  304. !['暂停', '停止', '死亡丧葬', '已归档'].includes(p.status),
  305. )
  306. .map((p) => String(p.id))
  307. return delay(ids)
  308. }
  309. return real.fetchBatchMemberIds(batchId)
  310. }
  311. /** 本批次人员明细(按发放明细,非全村人员库) */
  312. export function fetchBatchMemberPeoplePage(
  313. batchId: string,
  314. opts?: {
  315. batchNo?: string
  316. town?: string
  317. village?: string
  318. pruneDefaults?: boolean
  319. parentBatchId?: string
  320. pageNum?: number
  321. pageSize?: number
  322. },
  323. ) {
  324. if (useMock()) {
  325. const batch = mockVillageBatches.find((b) => b.id === batchId)
  326. if (!batch) return delay({ list: [] as BatchMemberPerson[], total: 0, pageNum: 1, pageSize: 10 })
  327. const list = mockPeople
  328. .filter(
  329. (p) =>
  330. p.town === batch.town &&
  331. p.village === batch.village &&
  332. p.batchNo === batch.batchNo &&
  333. !['暂停', '停止', '死亡丧葬', '已归档'].includes(p.status),
  334. )
  335. .map((p) => mockAsBatchMember(p))
  336. return mockPaged(list, opts?.pageNum, opts?.pageSize)
  337. }
  338. return real.fetchBatchMemberPeoplePage(batchId, opts)
  339. }
  340. export function fetchBatchMemberPeople(
  341. batchId: string,
  342. opts?: {
  343. batchNo?: string
  344. town?: string
  345. village?: string
  346. pruneDefaults?: boolean
  347. parentBatchId?: string
  348. pageNum?: number
  349. pageSize?: number
  350. },
  351. ) {
  352. if (useMock()) {
  353. return fetchBatchMemberPeoplePage(batchId, opts).then((p) => p.list)
  354. }
  355. return real.fetchBatchMemberPeople(batchId, opts)
  356. }
  357. /** 本村发放历史(发放明细行;金额来自明细,非人员底数冗余标准) */
  358. export function fetchVillagePaymentHistory(params?: { town?: string; village?: string }) {
  359. if (useMock()) {
  360. let list = [...mockPeople]
  361. if (params?.town) list = list.filter((p) => p.town === params.town)
  362. if (params?.village) list = list.filter((p) => p.village === params.village)
  363. return delay(list.map((p) => mockAsBatchMember(p)))
  364. }
  365. return real.fetchVillagePaymentHistory(params)
  366. }
  367. /** 指定人员历史发放明细 */
  368. export function fetchPersonnelPaymentHistory(personnelId: string) {
  369. if (useMock()) {
  370. const p = mockPeople.find((x) => String(x.id) === String(personnelId))
  371. return delay(p ? [mockAsBatchMember(p)] : [])
  372. }
  373. return real.fetchPersonnelPaymentHistory(personnelId)
  374. }
  375. /** 指定人员告警/处理记录 */
  376. export function fetchPersonnelAlerts(params: {
  377. personnelId?: string
  378. personName?: string
  379. town?: string
  380. village?: string
  381. }) {
  382. if (useMock()) {
  383. return delay(
  384. mockAlerts.filter(
  385. (a) =>
  386. (!params.personName || a.personName === params.personName) &&
  387. (!params.town || a.town === params.town) &&
  388. (!params.village || !a.village || a.village === params.village),
  389. ),
  390. )
  391. }
  392. return real.fetchPersonnelAlerts(params)
  393. }
  394. function mockAsBatchMember(p: Person): BatchMemberPerson {
  395. const monthlyAmount = Number(p.standard || 0)
  396. const supplementAmount = 0
  397. return {
  398. ...p,
  399. monthlyAmount,
  400. oneTimeAmount: 0,
  401. supplementAmount,
  402. funeralAmount: 0,
  403. deductAmount: 0,
  404. payableAmount: monthlyAmount + supplementAmount,
  405. }
  406. }
  407. /** 村合作社提交批次 → 镇经发中心初审 */
  408. export function submitVillageBatch(batchId: string) {
  409. if (useMock()) {
  410. const batch = mockVillageBatches.find((b) => b.id === batchId)
  411. if (batch && (batch.stage === '人员新增录入' || batch.stage === '退回修改')) {
  412. batch.stage = '镇经发中心初审'
  413. batch.submittedAt = nowStr()
  414. mockApprovals.unshift({
  415. id: `AP${Date.now()}`,
  416. bizType: '月度批次人员申请',
  417. batchNo: batch.batchNo,
  418. town: batch.town,
  419. village: batch.village,
  420. title: `${batch.village}${batch.month}批次人员申请`,
  421. applicant: `${batch.village}合作社`,
  422. node: '镇经发中心初审',
  423. status: '待审批',
  424. createdAt: nowStr(),
  425. })
  426. mockLogs.unshift({
  427. id: Date.now(),
  428. time: nowStr(),
  429. user: '村合作社',
  430. role: '村合作社',
  431. module: '批次',
  432. action: `提交初审 ${batch.batchNo}`,
  433. result: '成功',
  434. })
  435. }
  436. return delay({ ok: true })
  437. }
  438. return real.submitVillageBatch(batchId)
  439. }
  440. /** 批次详情 */
  441. export function fetchBatchDetail(batchId: string) {
  442. if (useMock()) {
  443. const b = mockVillageBatches.find((x) => x.id === batchId)
  444. return delay(b ? { ...b } : null)
  445. }
  446. return real.fetchBatchDetail(batchId)
  447. }
  448. /** 批次流程总览 */
  449. export function fetchBatchOverview(batchId: string) {
  450. if (useMock()) {
  451. const b = mockVillageBatches.find((x) => x.id === batchId)
  452. return delay(
  453. b
  454. ? {
  455. batchId: b.id,
  456. batchNo: b.batchNo,
  457. currentStage: b.stage,
  458. status: b.stage,
  459. stageDescription: `当前处于「${b.stage}」`,
  460. availableActions:
  461. b.stage === '人员新增录入' || b.stage === '退回修改'
  462. ? ['SUBMIT_BATCH']
  463. : [],
  464. recentLogs: [
  465. {
  466. id: '1',
  467. stage: b.stage,
  468. action: '创建批次',
  469. operator: b.submitter || '系统',
  470. result: '成功',
  471. operateTime: b.submittedAt || nowStr(),
  472. },
  473. ],
  474. }
  475. : null,
  476. )
  477. }
  478. return real.fetchBatchOverview(batchId)
  479. }
  480. /** 批次流程时间线 */
  481. export function fetchBatchTimeline(batchId: string) {
  482. if (useMock()) {
  483. const b = mockVillageBatches.find((x) => x.id === batchId)
  484. if (!b) return delay([])
  485. return delay([
  486. {
  487. id: 'm1',
  488. batchId: b.id,
  489. batchNo: b.batchNo,
  490. stage: '人员新增录入',
  491. action: '创建本月村批',
  492. actionLabel: '创建本月村批',
  493. operator: '系统',
  494. result: '成功',
  495. operateTime: nowStr(),
  496. },
  497. ...(b.submittedAt
  498. ? [
  499. {
  500. id: 'm2',
  501. batchId: b.id,
  502. batchNo: b.batchNo,
  503. stage: b.stage,
  504. action: '提交/流转',
  505. actionLabel: '提交/流转',
  506. operator: b.submitter || '经办人',
  507. result: '成功',
  508. operateTime: b.submittedAt,
  509. },
  510. ]
  511. : []),
  512. ])
  513. }
  514. return real.fetchBatchTimeline(batchId)
  515. }
  516. /** 人员状态变更 / 特殊业务时间线 */
  517. export function fetchPersonnelTimeline(personnelId: string | number) {
  518. if (useMock()) {
  519. const p = mockPeople.find((x) => String(x.id) === String(personnelId))
  520. return delay([
  521. {
  522. id: `pl-${personnelId}-1`,
  523. action: 'STATUS_NORMAL',
  524. actionLabel: '纳入在册',
  525. operator: '系统',
  526. operatorRole: '业务部门',
  527. opinion: p ? `${p.name} 基础信息建档` : '建档',
  528. result: '成功',
  529. operateTime: nowStr(),
  530. remarks: p?.remark,
  531. },
  532. ] as import('@/types/biz').ProcessLogItem[])
  533. }
  534. return real.fetchPersonnelTimeline(String(personnelId))
  535. }
  536. /** 补充流程备注 */
  537. export function addBatchProcessNote(payload: {
  538. batchId: string
  539. opinion: string
  540. fileUrls?: string
  541. }) {
  542. if (useMock()) {
  543. mockLogs.unshift({
  544. id: Date.now(),
  545. time: nowStr(),
  546. user: '演示用户',
  547. role: getAppConfig().currentRole,
  548. module: '流程备注',
  549. action: payload.opinion,
  550. result: '成功',
  551. })
  552. return delay({ ok: true })
  553. }
  554. return real.addBatchProcessNote(payload)
  555. }
  556. /* ========== 居保匹配 ========== */
  557. export function fetchInsuranceBatchesPage(params?: {
  558. tab?: 'todo' | 'history'
  559. batchNo?: string
  560. stage?: string
  561. pageNum?: number
  562. pageSize?: number
  563. }) {
  564. if (useMock()) {
  565. const todo = new Set(['居保状态匹配', '比对结果已生成'])
  566. const history = new Set([
  567. '分管领导审批',
  568. '待线下发放',
  569. '回盘信息确认',
  570. '已回盘确认',
  571. '已归档',
  572. ])
  573. const stages = params?.tab === 'history' ? history : todo
  574. let list = mockVillageBatches.filter((b) => stages.has(b.stage))
  575. if (params?.batchNo) list = list.filter((b) => b.batchNo.includes(params.batchNo!))
  576. if (params?.stage) list = list.filter((b) => b.stage === params.stage)
  577. return mockPaged(list, params?.pageNum, params?.pageSize)
  578. }
  579. return real.fetchInsuranceBatchesPage(params)
  580. }
  581. export function fetchInsuranceBatches(params?: { tab?: 'todo' | 'history' }) {
  582. if (useMock()) {
  583. return fetchInsuranceBatchesPage(params).then((p) => p.list)
  584. }
  585. return real.fetchInsuranceBatches(params)
  586. }
  587. /** 查询居保匹配结果;可按批次号 / 街镇 / 村居过滤 */
  588. export function fetchInsuranceResultsPage(params?: {
  589. batchId?: string
  590. batchNo?: string
  591. town?: string
  592. village?: string
  593. pageNum?: number
  594. pageSize?: number
  595. }) {
  596. if (useMock()) {
  597. let list = [...mockPeople]
  598. if (params?.batchNo) list = list.filter((p) => p.batchNo === params.batchNo)
  599. if (params?.town) list = list.filter((p) => p.town === params.town)
  600. if (params?.village) list = list.filter((p) => p.village === params.village)
  601. return mockPaged(list, params?.pageNum, params?.pageSize)
  602. }
  603. return real.fetchInsuranceResultsPage(params)
  604. }
  605. export function fetchInsuranceResults(params?: {
  606. batchId?: string
  607. batchNo?: string
  608. town?: string
  609. village?: string
  610. pageNum?: number
  611. pageSize?: number
  612. }) {
  613. if (useMock()) {
  614. return fetchInsuranceResultsPage(params).then((p) => p.list)
  615. }
  616. return real.fetchInsuranceResults(params)
  617. }
  618. /** 导出居保匹配结果 xlsx */
  619. export function exportInsuranceMatch(params: {
  620. batchId?: string
  621. town?: string
  622. townId?: string
  623. }) {
  624. if (useMock()) {
  625. return delay({ ok: false as const, fileName: '', message: '演示模式不支持导出' })
  626. }
  627. return real.exportInsuranceMatch(params)
  628. }
  629. /** 触发居保匹配:产出比对结果清单,批次进入「比对结果已生成」;支持单条或批量 */
  630. export function triggerInsuranceMatch(batchIds?: string | string[]) {
  631. if (useMock()) {
  632. const ids = batchIds == null ? null : Array.isArray(batchIds) ? batchIds : [batchIds]
  633. const targets = ids
  634. ? mockVillageBatches.filter((b) => ids.includes(b.id) && b.stage === '居保状态匹配')
  635. : mockVillageBatches.filter((b) => b.stage === '居保状态匹配')
  636. for (const b of targets) {
  637. b.stage = '比对结果已生成'
  638. }
  639. return delay({ ok: true, count: targets.length, lastSummary: null })
  640. }
  641. return real.triggerInsuranceMatch(batchIds)
  642. }
  643. /** 居保匹配任务执行日志 */
  644. export function fetchInsuranceTaskLogs(batchId: string, limit = 30) {
  645. if (useMock()) return delay([] as Record<string, unknown>[])
  646. return real.fetchInsuranceTaskLogs(batchId, limit)
  647. }
  648. /** 人工确认居保匹配结果 */
  649. export function confirmInsuranceManual(
  650. personId: number | string,
  651. opts?: { batchId?: string; pass?: boolean; remark?: string },
  652. ) {
  653. if (useMock()) {
  654. const p = mockPeople.find((x) => x.id === personId)
  655. if (p) {
  656. if (opts?.pass === false) {
  657. p.insuranceStatus = '匹配失败'
  658. p.status = '暂停'
  659. p.remark = opts.remark || '人工确认暂停发放'
  660. } else {
  661. p.insuranceStatus = '匹配通过'
  662. p.status = '正常在库'
  663. p.remark = opts?.remark || '人工确认匹配成功'
  664. }
  665. }
  666. return delay({ ok: true })
  667. }
  668. return real.confirmInsuranceManual(personId, opts)
  669. }
  670. /** 业务部门确认比对结果清单后提交分管领导审批 */
  671. export function submitToLeader(batchId: string) {
  672. if (useMock()) {
  673. const b = mockVillageBatches.find((x) => x.id === batchId)
  674. if (!b || b.stage !== '比对结果已生成') {
  675. return delay({ ok: false, message: '仅待提交批次可提交分管领导审核' })
  676. }
  677. b.stage = '分管领导审批'
  678. b.submittedAt = nowStr()
  679. mockApprovals.unshift({
  680. id: `AP${Date.now()}${b.id}`,
  681. bizType: '月补贴清单确认',
  682. batchNo: b.batchNo,
  683. town: b.town,
  684. village: b.village,
  685. title: `${b.town}${b.village}月补贴清单提交分管领导`,
  686. applicant: `${b.town}社区事务受理中心`,
  687. node: '分管领导线上审批',
  688. status: '待审批',
  689. createdAt: nowStr(),
  690. })
  691. return delay({ ok: true })
  692. }
  693. return real.submitToLeader(batchId)
  694. }
  695. /** 导出比对结果清单(演示) */
  696. export function exportCompareList(_batchId?: string) {
  697. if (useMock()) return delay({ ok: true, fileName: `${getAppConfig().currentMonth.replace('-', '')}-比对结果清单.xlsx` })
  698. return real.exportCompareList(_batchId)
  699. }
  700. /** 居保异常批次退回镇经发中心 */
  701. export function returnBatchToTown(batchId: string, reason?: string) {
  702. if (useMock()) {
  703. const batch = mockVillageBatches.find((b) => b.id === batchId)
  704. if (batch) {
  705. batch.stage = '镇经发中心初审'
  706. mockApprovals.unshift({
  707. id: `AP${Date.now()}`,
  708. bizType: '居保匹配退回',
  709. batchNo: batch.batchNo,
  710. town: batch.town,
  711. village: batch.village,
  712. title: `${batch.village}居保匹配异常退回初审`,
  713. applicant: `${batch.town}社区事务受理中心`,
  714. node: '镇经发中心初审',
  715. status: '待审批',
  716. opinion: reason || '居保匹配异常,退回镇经发中心',
  717. createdAt: nowStr(),
  718. })
  719. }
  720. return delay({ ok: true })
  721. }
  722. return real.returnBatchToTown(batchId, reason)
  723. }
  724. /* ========== 审批 ========== */
  725. export function fetchApprovals(params?: {
  726. tab?: string
  727. node?: string
  728. /** 街镇(镇经发 / 分管领导仅看本镇) */
  729. town?: string
  730. village?: string
  731. /** 统一批次号,如 202608 */
  732. batchNo?: string
  733. /** 业务类型包含关键字,如「月度批次」 */
  734. bizTypeIncludes?: string
  735. status?: string
  736. pageNum?: number
  737. pageSize?: number
  738. }) {
  739. if (useMock()) {
  740. let list = [...mockApprovals]
  741. if (params?.tab === 'pending') list = list.filter((a) => a.status === '待审批')
  742. if (params?.tab === 'history') {
  743. list = list.filter((a) => a.status === '已通过' || a.status === '已驳回')
  744. }
  745. if (params?.node) list = list.filter((a) => a.node === params.node)
  746. if (params?.town) list = list.filter((a) => a.town === params.town)
  747. if (params?.village) list = list.filter((a) => a.village === params.village)
  748. if (params?.batchNo) list = list.filter((a) => a.batchNo === params.batchNo)
  749. if (params?.status) list = list.filter((a) => a.status === params.status)
  750. if (params?.bizTypeIncludes) {
  751. list = list.filter((a) => a.bizType.includes(params.bizTypeIncludes!))
  752. }
  753. return mockPaged(list, params?.pageNum, params?.pageSize)
  754. }
  755. return real.fetchApprovals(params)
  756. }
  757. /**
  758. * 镇经发中心:核验新增人员退养资格
  759. * 写入 retireQualifyType;人员状态仍跟 bizStatus(无资格除外)
  760. */
  761. export function reviewPersonQualify(payload: {
  762. personId: number | string
  763. retireQualifyType: '有土地退养' | '无土地有资格' | '无资格'
  764. }) {
  765. if (useMock()) {
  766. const p = mockPeople.find((x) => x.id === payload.personId)
  767. if (!p) return delay({ ok: false })
  768. p.retireQualifyType = payload.retireQualifyType
  769. if (payload.retireQualifyType === '无资格') {
  770. p.status = '无资格'
  771. p.remark = [p.remark, '无资格;不具备土地退养补助资格'].filter(Boolean).join(';')
  772. } else if (payload.retireQualifyType === '无土地有资格') {
  773. p.remark = p.remark?.includes('无土地')
  774. ? p.remark
  775. : [p.remark, '无承包地,按政策认定具备退养补助资格'].filter(Boolean).join(';')
  776. }
  777. return delay({ ok: true, person: { ...p } })
  778. }
  779. return real.reviewPersonQualify(payload)
  780. }
  781. export function decideApproval(payload: {
  782. id: string
  783. pass: boolean
  784. opinion?: string
  785. fileUrls?: string
  786. retireQualifyType?: '有土地退养' | '无土地有资格' | '无资格'
  787. }) {
  788. if (useMock()) {
  789. const appr = mockApprovals.find((a) => a.id === payload.id)
  790. if (!appr) return delay({ ok: false })
  791. appr.status = payload.pass ? '已通过' : '已驳回'
  792. appr.opinion = payload.opinion || (payload.pass ? '同意' : '退回修改')
  793. const batch = mockVillageBatches.find(
  794. (b) =>
  795. b.batchNo === appr.batchNo &&
  796. b.town === appr.town &&
  797. (!appr.village || b.village === appr.village),
  798. )
  799. if (appr.node.includes('镇经发')) {
  800. if (payload.pass && batch) {
  801. batch.stage = '居保状态匹配'
  802. } else if (!payload.pass && batch) {
  803. batch.stage = '退回修改'
  804. }
  805. } else if (appr.node.includes('分管领导')) {
  806. if (payload.pass && batch) {
  807. // 分管领导通过后进入「已审核」,由业务部门执行出盘
  808. batch.stage = '待线下发放'
  809. const monthly = mockMonthlyBatches.find(
  810. (m) => m.batchNo === batch.batchNo && m.town === batch.town,
  811. )
  812. if (monthly) monthly.status = '已审核'
  813. } else if (!payload.pass && batch) {
  814. batch.stage = '居保状态匹配'
  815. }
  816. // 特殊业务(一般/丧葬/调标补发):通过后状态回写,驳回回业务受理
  817. const sp = mockSpecials.find(
  818. (s) =>
  819. s.status === '待分管领导审批' &&
  820. s.town === appr.town &&
  821. (appr.title.includes(s.personName) || appr.bizType.includes(s.type.slice(0, 2))),
  822. )
  823. if (sp) {
  824. sp.status = payload.pass ? '已通过' : '已驳回(补充/修改)'
  825. }
  826. }
  827. return delay({ ok: true })
  828. }
  829. return real.decideApproval(payload)
  830. }
  831. export function decideApprovals(payload: {
  832. ids: string[]
  833. pass: boolean
  834. opinion?: string
  835. fileUrls?: string
  836. retireQualifyType?: '有土地退养' | '无土地有资格' | '无资格'
  837. }) {
  838. if (useMock()) {
  839. return Promise.all(payload.ids.map((id) => decideApproval({ ...payload, id }))).then(() => ({
  840. ok: true,
  841. }))
  842. }
  843. return real.decideApprovals(payload)
  844. }
  845. /* ========== 人员状态维护 ========== */
  846. export function fetchStatusBatches() {
  847. if (useMock()) {
  848. return delay(
  849. mockVillageBatches.filter(
  850. (b) =>
  851. b.stage === '分管领导审批' ||
  852. b.stage === '待线下发放' ||
  853. b.stage === '回盘信息确认',
  854. ),
  855. )
  856. }
  857. return real.fetchStatusBatches()
  858. }
  859. /** 补助标准列表;传 town 时仅返回该镇各年度标准 */
  860. export function fetchStandards(town?: string) {
  861. if (useMock()) {
  862. const list = town ? mockStandards.filter((s) => s.town === town) : [...mockStandards]
  863. return delay(
  864. [...list].sort((a, b) => (b.year !== a.year ? b.year - a.year : b.date.localeCompare(a.date))),
  865. )
  866. }
  867. return real.fetchStandards(town)
  868. }
  869. /** 本镇当前有效丧葬补贴金额(元) */
  870. export async function fetchCurrentFuneralAmount(town: string): Promise<number | null> {
  871. if (useMock()) {
  872. const list = town ? mockStandards.filter((s) => s.town === town) : [...mockStandards]
  873. const sorted = [...list].sort((a, b) =>
  874. b.year !== a.year ? b.year - a.year : b.date.localeCompare(a.date),
  875. )
  876. const hit = sorted.find((s) => s.funeral > 0)
  877. return delay(hit ? hit.funeral : null)
  878. }
  879. return real.fetchCurrentFuneralAmount(town)
  880. }
  881. /** 新增年度补助标准 */
  882. export function createStandard(payload: {
  883. town: string
  884. year: number
  885. monthly: number
  886. funeral: number
  887. date: string
  888. reason: string
  889. }) {
  890. if (useMock()) {
  891. const dup = mockStandards.find((s) => s.town === payload.town && s.year === payload.year)
  892. if (dup) {
  893. return delay({ ok: false as const, message: `${payload.year} 年度标准已存在,请勿重复新增` })
  894. }
  895. const id = Math.max(0, ...mockStandards.map((s) => s.id)) + 1
  896. mockStandards.unshift({ id, ...payload })
  897. return delay({ ok: true as const, id })
  898. }
  899. return real.createStandard(payload)
  900. }
  901. /** 调整最新补助标准 */
  902. export function updateStandard(payload: {
  903. id: number
  904. monthly: number
  905. funeral: number
  906. date: string
  907. reason: string
  908. rawId?: string
  909. town?: string
  910. }) {
  911. if (useMock()) {
  912. const row = mockStandards.find((s) => s.id === payload.id)
  913. if (!row) return delay({ ok: false as const, message: '未找到该标准记录' })
  914. row.monthly = payload.monthly
  915. row.funeral = payload.funeral
  916. row.date = payload.date
  917. row.reason = payload.reason
  918. return delay({ ok: true as const })
  919. }
  920. return real.updateStandard(payload)
  921. }
  922. /** 管理员删除补助标准 */
  923. export function deleteStandard(payload: { id: number; rawId?: string }) {
  924. if (useMock()) {
  925. const idx = mockStandards.findIndex((s) => s.id === payload.id)
  926. if (idx < 0) return delay({ ok: false as const, message: '未找到该标准记录' })
  927. mockStandards.splice(idx, 1)
  928. return delay({ ok: true as const, message: '已删除' })
  929. }
  930. return real.deleteStandard(payload)
  931. }
  932. /** 死亡状态更新(业务部门) */
  933. export function updateDeathStatus(deathId: string) {
  934. if (useMock()) {
  935. const death = mockDeaths.find((d) => d.id === deathId)
  936. if (death) {
  937. death.status = '已停止发放'
  938. const alert = mockAlerts.find(
  939. (a) => a.type === '民政死亡比对' && a.personName === death.personName,
  940. )
  941. if (alert) {
  942. alert.status = '已关闭'
  943. alert.result = '已完成死亡状态更新,停止发放'
  944. }
  945. }
  946. return delay({ ok: true })
  947. }
  948. return real.updateDeathStatus(deathId)
  949. }
  950. export function applyPersonStatusAction(payload: {
  951. personId: number | string
  952. action: string
  953. /** 补发金额,可为负数 */
  954. amount?: number
  955. reason?: string
  956. fileUrls?: string
  957. }) {
  958. if (useMock()) {
  959. const p = mockPeople.find((x) => x.id === payload.personId)
  960. if (p) {
  961. if (payload.action === '停止' || payload.action === '暂停/停止') {
  962. p.status = '停止'
  963. } else if (payload.action === '恢复') {
  964. p.status = '正常在库'
  965. } else if (payload.action === '丧葬' || payload.action === '死亡状态更新') {
  966. p.status = '死亡丧葬'
  967. } else if (payload.action === '补发' || payload.action === '调标补发') {
  968. const amountTip =
  969. typeof payload.amount === 'number' ? `金额 ${payload.amount} 元` : ''
  970. const reasonTip = payload.reason ? `:${payload.reason}` : ''
  971. p.remark = `${p.remark || ''}(补发待审批${amountTip}${reasonTip})`.trim()
  972. }
  973. }
  974. return delay({ ok: true })
  975. }
  976. return real.applyPersonStatusAction(payload)
  977. }
  978. /* ========== 特殊业务 ========== */
  979. export function fetchSpecialsPage(params?: {
  980. town?: string
  981. bizType?: string
  982. approveStatus?: string
  983. pageNum?: number
  984. pageSize?: number
  985. }) {
  986. if (useMock()) {
  987. let list = params?.town ? mockSpecials.filter((s) => s.town === params.town) : [...mockSpecials]
  988. if (params?.bizType === 'FUNERAL') list = list.filter((s) => s.type === '丧葬补贴')
  989. if (params?.bizType === 'REFUND') list = list.filter((s) => s.type === '多发退款')
  990. return mockPaged(list, params?.pageNum, params?.pageSize)
  991. }
  992. return real.fetchSpecialsPage(params)
  993. }
  994. export function fetchSpecials(params?: { town?: string }) {
  995. if (useMock()) {
  996. return fetchSpecialsPage(params).then((p) => p.list)
  997. }
  998. return real.fetchSpecials(params)
  999. }
  1000. /** 特殊业务审批详情:关联人员 */
  1001. export function fetchSpecialBizPerson(personnelId: string) {
  1002. if (useMock()) {
  1003. const p = mockPeople.find((x) => String(x.id) === String(personnelId))
  1004. return delay(p || null)
  1005. }
  1006. return real.fetchSpecialBizPerson(personnelId)
  1007. }
  1008. export function fetchDeathsPage(params?: { town?: string; pageNum?: number; pageSize?: number }) {
  1009. if (useMock()) {
  1010. const list = params?.town ? mockDeaths.filter((d) => d.town === params.town) : [...mockDeaths]
  1011. return mockPaged(list, params?.pageNum, params?.pageSize)
  1012. }
  1013. return real.fetchDeathsPage(params)
  1014. }
  1015. export function fetchDeaths(params?: { town?: string; pageNum?: number; pageSize?: number }) {
  1016. if (useMock()) {
  1017. return fetchDeathsPage(params).then((p) => p.list)
  1018. }
  1019. return real.fetchDeaths(params)
  1020. }
  1021. /** 镇社区事务中心:死亡人员信息录入 → 提交分管领导审批(通过后改人员状态) */
  1022. export function createDeathRecord(payload: {
  1023. personId?: string | number
  1024. personName: string
  1025. idCard: string
  1026. town: string
  1027. village: string
  1028. deathDate: string
  1029. heirName?: string
  1030. heirRelation?: string
  1031. heirIdCard?: string
  1032. heirBankCard?: string
  1033. heirContact?: string
  1034. fileUrls?: string
  1035. }) {
  1036. if (useMock()) {
  1037. const dup = mockDeaths.find(
  1038. (d) =>
  1039. (d.status === '待分管领导审批' || d.status === '待死亡状态更新') &&
  1040. (d.idCard === payload.idCard ||
  1041. (d.personName === payload.personName &&
  1042. d.town === payload.town &&
  1043. d.village === payload.village)),
  1044. )
  1045. if (dup) {
  1046. return delay({
  1047. ok: false,
  1048. message: '该人员已有待审批的死亡登记,请勿重复提交',
  1049. code: 'DUPLICATE_DEATH',
  1050. })
  1051. }
  1052. const id = `SP${Date.now()}`
  1053. mockDeaths.unshift({
  1054. id,
  1055. personnelId: payload.personId != null ? String(payload.personId) : undefined,
  1056. personName: payload.personName,
  1057. idCard: payload.idCard,
  1058. town: payload.town,
  1059. village: payload.village,
  1060. deathDate: payload.deathDate,
  1061. heirName: payload.heirName || '',
  1062. heirIdCard: payload.heirIdCard || '',
  1063. heirBankCard: payload.heirBankCard || '',
  1064. heirContact: payload.heirContact || '',
  1065. source: '社区事务受理中心手工录入',
  1066. status: '待分管领导审批',
  1067. createdAt: nowStr(),
  1068. fileUrls: payload.fileUrls,
  1069. reason: `死亡登记;死亡日期 ${payload.deathDate}`,
  1070. })
  1071. return delay({ ok: true, id })
  1072. }
  1073. return real.createDeathRecord(payload)
  1074. }
  1075. export function uploadBizFile(file: File) {
  1076. if (useMock()) {
  1077. return delay({ url: `mock://${file.name}`, name: file.name })
  1078. }
  1079. return real.uploadBizFile(file)
  1080. }
  1081. /** 发起一般特殊业务 / 丧葬补贴 */
  1082. export function createSpecialBiz(payload: {
  1083. type: string
  1084. personId?: string | number
  1085. personName: string
  1086. town: string
  1087. village: string
  1088. reason: string
  1089. amount: number
  1090. heirName?: string
  1091. fileUrls?: string
  1092. refundMethod?: 'DEDUCT_NEXT' | 'RETURN_FISCAL'
  1093. }) {
  1094. if (useMock()) {
  1095. const month = getAppConfig().currentMonth
  1096. const id = `SP${Date.now()}`
  1097. mockSpecials.unshift({
  1098. id,
  1099. batchNo: month.replace('-', ''),
  1100. town: payload.town,
  1101. village: payload.village,
  1102. type: payload.type,
  1103. personName: payload.personName,
  1104. reason: payload.reason,
  1105. amount: payload.amount,
  1106. status: '待分管领导审批',
  1107. heirName: payload.heirName,
  1108. refundMethod: payload.refundMethod,
  1109. refundMethodLabel:
  1110. payload.refundMethod === 'RETURN_FISCAL' ? '退缴财政' : payload.refundMethod ? '下月扣减' : undefined,
  1111. fileUrls: payload.fileUrls,
  1112. createdAt: nowStr(),
  1113. })
  1114. mockApprovals.unshift({
  1115. id: `AP${Date.now()}`,
  1116. bizType: payload.type,
  1117. batchNo: month.replace('-', ''),
  1118. town: payload.town,
  1119. village: payload.village,
  1120. title: `${payload.personName}${payload.type}申请`,
  1121. applicant: `${payload.town}社区事务受理中心`,
  1122. node: '分管领导线上审批',
  1123. status: '待审批',
  1124. createdAt: nowStr(),
  1125. })
  1126. return delay({ ok: true, id })
  1127. }
  1128. return real.createSpecialBiz(payload)
  1129. }
  1130. /** 特殊业务汇总文件导出(调标补发/丧葬/一般) */
  1131. export function exportSpecialSummary(type?: string) {
  1132. if (useMock()) {
  1133. const name = type ? `${getAppConfig().currentMonth.replace('-', '')}-${type}汇总文件.xlsx` : '特殊业务汇总文件.xlsx'
  1134. return delay({ ok: true, fileName: name })
  1135. }
  1136. return real.exportSpecialSummary(type)
  1137. }
  1138. /**
  1139. * 发起调标补发申请:
  1140. * 1. 在月补贴清单按本镇各村自动生成批次(待提交)
  1141. * 2. 生成各村调标补发名单(后续对接接口由后端返回)
  1142. */
  1143. export function triggerAdjustReissue(town?: string) {
  1144. if (useMock()) {
  1145. const month = getAppConfig().currentMonth
  1146. const batchNo = month.replace('-', '')
  1147. const targetTown = town || '叶榭镇'
  1148. const villages = getVillagesByTown(targetTown)
  1149. if (!villages.length) {
  1150. return delay({ ok: false, message: '未找到本镇村居配置', batchNo, count: 0, villageCount: 0 })
  1151. }
  1152. const newStandard = getStandardByTown(targetTown)
  1153. /** 演示:调标差额默认按新标准 - 原标准,原标准按新标准减 60 */
  1154. const diffPerPerson = 60
  1155. let personCount = 0
  1156. const stamp = Date.now()
  1157. // 清空本镇本批次旧名单后重建(演示可重复触发)
  1158. for (let i = mockAdjustReissueRoster.length - 1; i >= 0; i--) {
  1159. const row = mockAdjustReissueRoster[i]
  1160. if (row.town === targetTown && row.batchNo === batchNo) {
  1161. mockAdjustReissueRoster.splice(i, 1)
  1162. }
  1163. }
  1164. villages.forEach((village, vIdx) => {
  1165. // 月补贴清单:各村生成/更新为「待提交」批次(不覆盖已审核及之后状态)
  1166. let batch = mockVillageBatches.find(
  1167. (b) => b.batchNo === batchNo && b.town === targetTown && b.village === village,
  1168. )
  1169. const villagePeople = mockPeople.filter(
  1170. (p) =>
  1171. p.town === targetTown &&
  1172. p.village === village &&
  1173. p.batchNo === batchNo &&
  1174. !['死亡丧葬', '已停止发放'].includes(p.status),
  1175. )
  1176. const peopleCount = villagePeople.length || 5 + (vIdx % 4)
  1177. const totalAmount = peopleCount * diffPerPerson
  1178. const canResetStage = !batch ||
  1179. [
  1180. '人员新增录入',
  1181. '镇经发中心初审',
  1182. '居保状态匹配',
  1183. '比对结果已生成',
  1184. '退回修改',
  1185. ].includes(batch.stage)
  1186. if (batch && canResetStage) {
  1187. batch.stage = '比对结果已生成'
  1188. batch.peopleCount = Math.max(batch.peopleCount, peopleCount)
  1189. batch.totalAmount = Math.max(batch.totalAmount, totalAmount)
  1190. batch.submitter = `${targetTown}社区事务受理中心`
  1191. batch.submittedAt = nowStr()
  1192. } else if (!batch) {
  1193. batch = {
  1194. id: `VB${batchNo}ADJ${stamp}${vIdx}`,
  1195. batchNo,
  1196. month,
  1197. town: targetTown,
  1198. village,
  1199. peopleCount,
  1200. totalAmount,
  1201. stage: '比对结果已生成',
  1202. submitter: `${targetTown}社区事务受理中心`,
  1203. submittedAt: nowStr(),
  1204. }
  1205. mockVillageBatches.unshift(batch)
  1206. } else {
  1207. // 已进入审核/出盘等后续环节:保留原批次,仅补充名单
  1208. batch.peopleCount = Math.max(batch.peopleCount, peopleCount)
  1209. }
  1210. // 各村调标补发名单
  1211. const sourcePeople =
  1212. villagePeople.length > 0
  1213. ? villagePeople
  1214. : mockPeople
  1215. .filter((p) => p.town === targetTown && p.village === village)
  1216. .slice(0, Math.min(8, 3 + vIdx))
  1217. sourcePeople.forEach((p, pIdx) => {
  1218. const oldStandard = Math.max(0, (p.standard || newStandard) - diffPerPerson)
  1219. mockAdjustReissueRoster.push({
  1220. id: `AR${batchNo}${village}${p.id || pIdx}${stamp}`,
  1221. batchNo,
  1222. personId: p.id,
  1223. name: p.name,
  1224. idCard: p.idCard,
  1225. town: targetTown,
  1226. village,
  1227. oldStandard,
  1228. newStandard: p.standard || newStandard,
  1229. amount: diffPerPerson,
  1230. status: '待提交',
  1231. remark: '年度调标差额补发',
  1232. })
  1233. personCount += 1
  1234. })
  1235. // 若该村暂无人员,生成演示名单
  1236. if (!sourcePeople.length) {
  1237. for (let i = 0; i < 3; i++) {
  1238. mockAdjustReissueRoster.push({
  1239. id: `AR${batchNo}${village}D${i}${stamp}`,
  1240. batchNo,
  1241. personId: 0,
  1242. name: `演示人员${village}${i + 1}`,
  1243. idCard: `3101171958010${1000 + vIdx * 10 + i}`,
  1244. town: targetTown,
  1245. village,
  1246. oldStandard: newStandard - diffPerPerson,
  1247. newStandard,
  1248. amount: diffPerPerson,
  1249. status: '待提交',
  1250. remark: '年度调标差额补发(演示)',
  1251. })
  1252. personCount += 1
  1253. }
  1254. }
  1255. })
  1256. return delay({
  1257. ok: true,
  1258. batchNo,
  1259. villageCount: villages.length,
  1260. count: personCount,
  1261. })
  1262. }
  1263. return real.triggerAdjustReissue(town)
  1264. }
  1265. /** 查询调标补发名单(可按镇/批次/村过滤) */
  1266. export function fetchAdjustReissueRoster(params?: {
  1267. town?: string
  1268. batchNo?: string
  1269. village?: string
  1270. }) {
  1271. if (useMock()) {
  1272. let list = [...mockAdjustReissueRoster]
  1273. if (params?.town) list = list.filter((r) => r.town === params.town)
  1274. if (params?.batchNo) list = list.filter((r) => r.batchNo === params.batchNo)
  1275. if (params?.village) list = list.filter((r) => r.village === params.village)
  1276. return delay(list)
  1277. }
  1278. return real.fetchAdjustReissueRoster(params)
  1279. }
  1280. /* ========== 月补贴清单 ========== */
  1281. /** 村级批次内部阶段 → 清单展示状态 */
  1282. const MONTHLY_STAGE_STATUS: Record<string, string> = {
  1283. 比对结果已生成: '待提交',
  1284. 分管领导审批: '待审核',
  1285. 待线下发放: '已审核',
  1286. 回盘信息确认: '已出盘',
  1287. 已回盘确认: '已回盘',
  1288. 已归档: '已回盘',
  1289. }
  1290. const MONTHLY_LIST_STAGES = Object.keys(MONTHLY_STAGE_STATUS)
  1291. function buildVillageMonthlyItem(b: VillageBatch): VillageMonthlyBatch {
  1292. const people = mockPeople.filter(
  1293. (p) => p.batchNo === b.batchNo && p.town === b.town && p.village === b.village,
  1294. )
  1295. const stopped = new Set(['暂停', '停止', '死亡丧葬', '已停止发放'])
  1296. let normalAmount = 0
  1297. let deductAmount = 0
  1298. for (const p of people) {
  1299. if (stopped.has(p.status)) deductAmount += p.standard
  1300. else normalAmount += p.standard
  1301. }
  1302. if (!people.length) {
  1303. normalAmount = b.totalAmount
  1304. }
  1305. const extraAmount = Math.max(0, b.totalAmount - normalAmount)
  1306. return {
  1307. id: b.id,
  1308. batchNo: b.batchNo,
  1309. month: b.month,
  1310. town: b.town,
  1311. village: b.village,
  1312. peopleCount: b.peopleCount || people.length,
  1313. normalAmount,
  1314. extraAmount,
  1315. deductAmount,
  1316. totalAmount: b.totalAmount || normalAmount + extraAmount - deductAmount,
  1317. status: MONTHLY_STAGE_STATUS[b.stage] || b.stage,
  1318. stage: b.stage,
  1319. submitter: b.submitter,
  1320. submittedAt: b.submittedAt,
  1321. }
  1322. }
  1323. /** 本镇各村月补贴清单批次(待提交/待审核/已审核/已出盘/已回盘) */
  1324. export function fetchVillageMonthlyBatchesPage(params?: {
  1325. town?: string
  1326. pageNum?: number
  1327. pageSize?: number
  1328. }) {
  1329. if (useMock()) {
  1330. let list = mockVillageBatches.filter((b) => MONTHLY_LIST_STAGES.includes(b.stage))
  1331. if (params?.town) list = list.filter((b) => b.town === params.town)
  1332. const items = list.map(buildVillageMonthlyItem)
  1333. items.sort((a, b) => {
  1334. const ta = a.submittedAt || ''
  1335. const tb = b.submittedAt || ''
  1336. if (ta !== tb) return tb.localeCompare(ta)
  1337. return a.village.localeCompare(b.village)
  1338. })
  1339. return mockPaged(items, params?.pageNum, params?.pageSize)
  1340. }
  1341. return real.fetchVillageMonthlyBatchesPage(params)
  1342. }
  1343. export function fetchVillageMonthlyBatches(town?: string) {
  1344. if (useMock()) {
  1345. let list = mockVillageBatches.filter((b) => MONTHLY_LIST_STAGES.includes(b.stage))
  1346. if (town) list = list.filter((b) => b.town === town)
  1347. const items = list.map(buildVillageMonthlyItem)
  1348. // 同批次同村保留最新一条(按 submittedAt)
  1349. items.sort((a, b) => {
  1350. const ta = a.submittedAt || ''
  1351. const tb = b.submittedAt || ''
  1352. if (ta !== tb) return tb.localeCompare(ta)
  1353. return a.village.localeCompare(b.village)
  1354. })
  1355. return delay(items)
  1356. }
  1357. return real.fetchVillageMonthlyBatches(town)
  1358. }
  1359. export function fetchMonthlyBatches() {
  1360. if (useMock()) return delay([...mockMonthlyBatches])
  1361. return real.fetchMonthlyBatches()
  1362. }
  1363. export function calculateMonthly() {
  1364. if (useMock()) {
  1365. const month = getAppConfig().currentMonth
  1366. const batchNo = month.replace('-', '')
  1367. const exists = mockMonthlyBatches.find((m) => m.batchNo === batchNo)
  1368. if (!exists) {
  1369. mockMonthlyBatches.unshift({
  1370. id: `MB${batchNo}01`,
  1371. batchNo,
  1372. month,
  1373. town: '叶榭镇',
  1374. peopleCount: mockPeople.filter((p) => p.batchNo === batchNo).length,
  1375. normalAmount: 199680,
  1376. extraAmount: 0,
  1377. deductAmount: 0,
  1378. totalAmount: 199680,
  1379. status: '待复核',
  1380. })
  1381. }
  1382. return delay({ ok: true })
  1383. }
  1384. return real.calculateMonthly()
  1385. }
  1386. export function confirmMonthly(id: string) {
  1387. if (useMock()) {
  1388. const row = mockMonthlyBatches.find((m) => m.id === id)
  1389. if (row) {
  1390. row.status = '已生成拟发放文件'
  1391. const disk = mockDisks.find((d) => d.batchNo === row.batchNo && d.town === row.town)
  1392. if (disk) {
  1393. disk.status = '待回盘'
  1394. disk.returnStatus = '待确认'
  1395. disk.fileName = `${row.batchNo}-${row.town}月补贴发放文件.xlsx`
  1396. } else {
  1397. mockDisks.unshift({
  1398. id: `DP${row.batchNo}${row.town}`,
  1399. batchNo: row.batchNo,
  1400. town: row.town,
  1401. fileName: `${row.batchNo}-${row.town}月补贴发放文件.xlsx`,
  1402. peopleCount: row.peopleCount,
  1403. amount: row.totalAmount,
  1404. status: '待回盘',
  1405. returnStatus: '待确认',
  1406. failCount: 0,
  1407. })
  1408. }
  1409. }
  1410. return delay({ ok: true })
  1411. }
  1412. return real.confirmMonthly(id)
  1413. }
  1414. /** 导出月度拟发放文件(供线下签字与财务发放) */
  1415. export function exportMonthlyFile(id?: string) {
  1416. if (useMock()) {
  1417. const row = id
  1418. ? mockVillageBatches.find((m) => m.id === id) ||
  1419. mockMonthlyBatches.find((m) => m.id === id)
  1420. : mockMonthlyBatches[0]
  1421. const name = row
  1422. ? `${row.batchNo}-${row.town}${'village' in row && row.village ? row.village : ''}月补贴发放文件.xlsx`
  1423. : '月补贴发放文件.xlsx'
  1424. return delay({ ok: true, fileName: name })
  1425. }
  1426. return real.exportMonthlyFile(id)
  1427. }
  1428. /**
  1429. * 出盘:已审核村批次导出拟发放文件,进入「已出盘」
  1430. */
  1431. export function issueVillageMonthlyDisk(batchId: string) {
  1432. if (useMock()) {
  1433. const batch = mockVillageBatches.find((b) => b.id === batchId)
  1434. if (!batch || batch.stage !== '待线下发放') {
  1435. return delay({ ok: false, message: '仅已审核批次可出盘' })
  1436. }
  1437. batch.stage = '回盘信息确认'
  1438. const diskId = `DP${batch.batchNo}${batch.town}${batch.village}`
  1439. const exists = mockDisks.find((d) => d.id === diskId)
  1440. const fileName = `${batch.batchNo}-${batch.town}${batch.village}月补贴发放文件.xlsx`
  1441. if (exists) {
  1442. exists.status = '待回盘'
  1443. exists.returnStatus = '待确认'
  1444. exists.fileName = fileName
  1445. exists.peopleCount = batch.peopleCount
  1446. exists.amount = batch.totalAmount
  1447. } else {
  1448. mockDisks.unshift({
  1449. id: diskId,
  1450. batchNo: batch.batchNo,
  1451. town: batch.town,
  1452. fileName,
  1453. peopleCount: batch.peopleCount,
  1454. amount: batch.totalAmount,
  1455. status: '待回盘',
  1456. returnStatus: '待确认',
  1457. failCount: 0,
  1458. })
  1459. }
  1460. return delay({ ok: true, fileName })
  1461. }
  1462. return real.issueVillageMonthlyDisk(batchId)
  1463. }
  1464. /**
  1465. * 确认回盘:已出盘村批次登记回盘结果,进入「已回盘」
  1466. */
  1467. export function confirmVillageMonthlyReturn(payload: {
  1468. batchId: string
  1469. failCount?: number
  1470. }) {
  1471. if (useMock()) {
  1472. const batch = mockVillageBatches.find((b) => b.id === payload.batchId)
  1473. if (!batch || batch.stage !== '回盘信息确认') {
  1474. return delay({ ok: false, message: '仅已出盘批次可确认回盘' })
  1475. }
  1476. const fail = payload.failCount ?? 0
  1477. batch.stage = '已回盘确认'
  1478. const diskId = `DP${batch.batchNo}${batch.town}${batch.village}`
  1479. let disk = mockDisks.find((d) => d.id === diskId)
  1480. if (!disk) {
  1481. disk = mockDisks.find(
  1482. (d) => d.batchNo === batch.batchNo && d.town === batch.town && d.returnStatus === '待确认',
  1483. )
  1484. }
  1485. if (disk) {
  1486. disk.failCount = fail
  1487. disk.returnStatus = fail > 0 ? '部分失败' : '成功'
  1488. disk.status = '已回盘'
  1489. }
  1490. if (fail > 0) {
  1491. mockAlerts.unshift({
  1492. id: `AL${Date.now()}`,
  1493. batchNo: batch.batchNo,
  1494. personName: '回盘失败人员',
  1495. town: batch.town,
  1496. village: batch.village,
  1497. type: '回盘发放失败',
  1498. level: '高',
  1499. failReason: DISK_RETURN_FAIL_REASONS[0],
  1500. content: `回盘发放失败:${DISK_RETURN_FAIL_REASONS[0]},需纳入下月处理(${batch.town}${batch.village})`,
  1501. status: '待处理',
  1502. createdAt: nowStr(),
  1503. })
  1504. }
  1505. return delay({ ok: true })
  1506. }
  1507. return real.confirmVillageMonthlyReturn(payload)
  1508. }
  1509. /* ========== 回盘信息确认 ========== */
  1510. /** 回盘批次列表(按批次号从新到旧) */
  1511. export function fetchDisksPage(params?: { town?: string; pageNum?: number; pageSize?: number }) {
  1512. if (useMock()) {
  1513. const list = params?.town ? mockDisks.filter((d) => d.town === params.town) : [...mockDisks]
  1514. return mockPaged(
  1515. [...list].sort((a, b) => b.batchNo.localeCompare(a.batchNo)),
  1516. params?.pageNum,
  1517. params?.pageSize,
  1518. )
  1519. }
  1520. return real.fetchDisksPage(params)
  1521. }
  1522. export function fetchDisks(town?: string) {
  1523. if (useMock()) {
  1524. return fetchDisksPage({ town }).then((p) => p.list)
  1525. }
  1526. return real.fetchDisks(town)
  1527. }
  1528. /** 导出回盘来源:月度拟发放文件(线上清单) */
  1529. export function exportDisk() {
  1530. if (useMock()) {
  1531. const pending = mockDisks.find((d) => d.returnStatus === '待确认')
  1532. if (pending) {
  1533. pending.fileName =
  1534. pending.fileName || `${pending.batchNo}-${pending.town}月补贴发放文件.xlsx`
  1535. }
  1536. return delay({ ok: true })
  1537. }
  1538. return real.exportDisk()
  1539. }
  1540. /** 回盘信息确认(登记线下发放结果,兼容旧调用) */
  1541. export function confirmReturnDisk(payload?: {
  1542. id?: string
  1543. failCount?: number
  1544. }) {
  1545. if (useMock()) {
  1546. const row =
  1547. (payload?.id && mockDisks.find((d) => d.id === payload.id)) ||
  1548. mockDisks.find((d) => d.returnStatus === '待确认')
  1549. if (row) {
  1550. const fail = payload?.failCount ?? 0
  1551. row.failCount = fail
  1552. row.returnStatus = fail > 0 ? '部分失败' : '成功'
  1553. row.status = '已回盘'
  1554. if (fail > 0) {
  1555. const townPeople = mockPeople.filter(
  1556. (p) => p.town === row.town && p.batchNo === row.batchNo,
  1557. )
  1558. const byVillage = new Map<string, typeof townPeople>()
  1559. for (const p of townPeople) {
  1560. const list = byVillage.get(p.village) || []
  1561. list.push(p)
  1562. byVillage.set(p.village, list)
  1563. }
  1564. const villages =
  1565. byVillage.size > 0
  1566. ? [...byVillage.entries()].slice(0, Math.min(fail, byVillage.size))
  1567. : ([['同建村', []]] as [string, typeof townPeople][])
  1568. villages.forEach(([village, people], idx) => {
  1569. const person = people[0]
  1570. const reason = DISK_RETURN_FAIL_REASONS[idx % DISK_RETURN_FAIL_REASONS.length]
  1571. mockAlerts.unshift({
  1572. id: `AL${Date.now()}${idx}`,
  1573. batchNo: row.batchNo,
  1574. personName: person?.name || '回盘失败人员',
  1575. town: row.town,
  1576. village,
  1577. type: '回盘发放失败',
  1578. level: '高',
  1579. failReason: reason,
  1580. content: `回盘发放失败:${reason},需纳入下月处理(${row.town}${village})`,
  1581. status: '待处理',
  1582. createdAt: nowStr(),
  1583. idCard: person?.idCard,
  1584. bankCard: person?.bankCard,
  1585. bankName: person?.bankName,
  1586. socialInsuranceCode: person?.socialInsuranceCode,
  1587. })
  1588. mockDiskFailPeople.unshift({
  1589. id: `DF${Date.now()}${idx}`,
  1590. diskId: row.id,
  1591. batchNo: row.batchNo,
  1592. town: row.town,
  1593. village,
  1594. personName: person?.name || '回盘失败人员',
  1595. idCard: person?.idCard || '',
  1596. bankCard: person?.bankCard,
  1597. failReason: reason,
  1598. amount: person?.standard,
  1599. payStatus: '待确认',
  1600. })
  1601. })
  1602. }
  1603. }
  1604. return delay({ ok: true })
  1605. }
  1606. return real.confirmReturnDisk(payload)
  1607. }
  1608. /**
  1609. * 上传回盘文件:选择批次后上传本镇回盘文件
  1610. * 后台匹配失败人员名单、生成告警,并更新清单/回盘状态
  1611. */
  1612. export function uploadReturnDiskFile(payload: { id: string; file: File }) {
  1613. if (useMock()) {
  1614. const row = mockDisks.find((d) => d.id === payload.id)
  1615. if (!row) {
  1616. return delay({ ok: false as const, message: '未找到回盘记录', failCount: 0, alertCount: 0 })
  1617. }
  1618. if (row.returnStatus !== '待确认' || row.status !== '待回盘') {
  1619. return delay({
  1620. ok: false as const,
  1621. message: '该批次已上传回盘文件,请勿重复上传',
  1622. failCount: 0,
  1623. alertCount: 0,
  1624. })
  1625. }
  1626. const townPeople = mockPeople.filter(
  1627. (p) => p.town === row.town && p.batchNo === row.batchNo && p.status === '正常在库',
  1628. )
  1629. const pool = townPeople.length
  1630. ? townPeople
  1631. : mockPeople.filter((p) => p.town === row.town).slice(0, 5)
  1632. /** 演示:按文件名哈希模拟失败人数(0~3) */
  1633. const nameSeed = [...payload.file.name].reduce((s, c) => s + c.charCodeAt(0), 0)
  1634. const failCount = pool.length ? Math.min(pool.length, nameSeed % 4) : 0
  1635. const failed = pool.slice(0, failCount)
  1636. // 清理该盘旧失败明细后重建
  1637. for (let i = mockDiskFailPeople.length - 1; i >= 0; i--) {
  1638. if (mockDiskFailPeople[i].diskId === row.id) mockDiskFailPeople.splice(i, 1)
  1639. }
  1640. row.returnFileName = payload.file.name
  1641. row.failCount = failCount
  1642. row.returnStatus = failCount > 0 ? '部分失败' : '成功'
  1643. row.status = '已回盘'
  1644. failed.forEach((person, idx) => {
  1645. const reason = DISK_RETURN_FAIL_REASONS[idx % DISK_RETURN_FAIL_REASONS.length]
  1646. mockDiskFailPeople.unshift({
  1647. id: `DF${Date.now()}${idx}`,
  1648. diskId: row.id,
  1649. batchNo: row.batchNo,
  1650. town: person.town,
  1651. village: person.village,
  1652. personName: person.name,
  1653. idCard: person.idCard,
  1654. bankCard: person.bankCard,
  1655. failReason: reason,
  1656. amount: person.standard,
  1657. payStatus: '待确认',
  1658. })
  1659. mockAlerts.unshift({
  1660. id: `AL${Date.now()}${idx}`,
  1661. batchNo: row.batchNo,
  1662. personName: person.name,
  1663. town: person.town,
  1664. village: person.village,
  1665. type: '回盘发放失败',
  1666. level: '高',
  1667. failReason: reason,
  1668. content: `回盘发放失败:${reason}(来源文件:${payload.file.name})`,
  1669. status: '待处理',
  1670. createdAt: nowStr(),
  1671. idCard: person.idCard,
  1672. bankCard: person.bankCard,
  1673. bankName: person.bankName,
  1674. socialInsuranceCode: person.socialInsuranceCode,
  1675. })
  1676. })
  1677. return delay({
  1678. ok: true as const,
  1679. failCount,
  1680. alertCount: failCount,
  1681. message:
  1682. failCount > 0
  1683. ? `回盘文件已解析:${failCount} 人发放失败,已匹配人员并生成告警`
  1684. : '回盘文件已解析:全部发放成功',
  1685. })
  1686. }
  1687. const form = new FormData()
  1688. form.append('id', payload.id)
  1689. form.append('file', payload.file)
  1690. return real.uploadReturnDiskFile(payload)
  1691. }
  1692. /** 查询某回盘批次的发放失败人员清单 */
  1693. export function fetchDiskFailPeople(diskId: string) {
  1694. if (useMock()) {
  1695. return delay(mockDiskFailPeople.filter((p) => p.diskId === diskId))
  1696. }
  1697. return real.fetchDiskFailPeople(diskId)
  1698. }
  1699. /** 确认失败人员已完成发放 */
  1700. export function confirmDiskFailPaid(failPersonId: string) {
  1701. if (useMock()) {
  1702. const row = mockDiskFailPeople.find((p) => p.id === failPersonId)
  1703. if (!row) return delay({ ok: false as const, message: '未找到该失败人员记录' })
  1704. if (row.payStatus === '已确认发放') {
  1705. return delay({ ok: false as const, message: '该人员已确认发放' })
  1706. }
  1707. row.payStatus = '已确认发放'
  1708. return delay({ ok: true as const })
  1709. }
  1710. return real.confirmDiskFailPaid(failPersonId)
  1711. }
  1712. /** @deprecated 兼容旧调用,等同 confirmReturnDisk */
  1713. export function importReturnDisk() {
  1714. return confirmReturnDisk()
  1715. }
  1716. /** 确认进入下月周期 */
  1717. export function archiveToNextMonth(id?: string) {
  1718. if (useMock()) {
  1719. const row =
  1720. (id && mockDisks.find((d) => d.id === id)) ||
  1721. mockDisks.find((d) => d.status === '已回盘')
  1722. if (row) {
  1723. row.status = '已归档'
  1724. const monthly = mockMonthlyBatches.find(
  1725. (m) => m.batchNo === row.batchNo && m.town === row.town,
  1726. )
  1727. if (monthly) monthly.status = '已归档'
  1728. const batches = mockVillageBatches.filter(
  1729. (b) => b.batchNo === row.batchNo && mTownVillageMatch(b, row),
  1730. )
  1731. for (const b of batches) {
  1732. if (b.stage === '回盘信息确认') b.stage = '已归档'
  1733. }
  1734. }
  1735. return delay({ ok: true })
  1736. }
  1737. return real.archiveToNextMonth(id)
  1738. }
  1739. function mTownVillageMatch(
  1740. b: { town: string; village?: string },
  1741. row: { town: string; village?: string },
  1742. ) {
  1743. return b.town === row.town && (!row.village || b.village === row.village)
  1744. }
  1745. /* ========== 告警 ========== */
  1746. export function fetchAlertsPage(params?: {
  1747. town?: string
  1748. village?: string
  1749. pageNum?: number
  1750. pageSize?: number
  1751. }) {
  1752. if (useMock()) {
  1753. let list = [...mockAlerts]
  1754. if (params?.town) list = list.filter((a) => a.town === params.town)
  1755. if (params?.village) list = list.filter((a) => a.village === params.village)
  1756. return mockPaged(list, params?.pageNum, params?.pageSize)
  1757. }
  1758. return real.fetchAlertsPage(params)
  1759. }
  1760. export function fetchAlerts() {
  1761. if (useMock()) return delay([...mockAlerts])
  1762. return real.fetchAlerts()
  1763. }
  1764. export function handleAlert(payload: HandleAlertPayload | string, result?: string) {
  1765. if (useMock()) {
  1766. // 兼容旧调用:handleAlert(id, result)
  1767. const data: HandleAlertPayload =
  1768. typeof payload === 'string'
  1769. ? {
  1770. id: payload,
  1771. personName: '',
  1772. idCard: '',
  1773. town: '',
  1774. village: '',
  1775. bankCard: '',
  1776. bankName: '',
  1777. socialInsuranceCode: '',
  1778. heirName: '',
  1779. heirIdCard: '',
  1780. heirBankCard: '',
  1781. heirContact: '',
  1782. result,
  1783. }
  1784. : payload
  1785. const a = mockAlerts.find((x) => x.id === data.id)
  1786. if (a) {
  1787. const oldName = a.personName
  1788. if (data.personName) a.personName = data.personName
  1789. if (data.town) a.town = data.town
  1790. if (data.village) a.village = data.village
  1791. a.idCard = data.idCard || a.idCard
  1792. a.bankCard = data.bankCard || a.bankCard
  1793. a.bankName = data.bankName || a.bankName
  1794. a.socialInsuranceCode = data.socialInsuranceCode || a.socialInsuranceCode
  1795. a.heirName = data.heirName
  1796. a.heirIdCard = data.heirIdCard
  1797. a.heirBankCard = data.heirBankCard
  1798. a.heirContact = data.heirContact
  1799. a.status = '已关闭'
  1800. a.result =
  1801. data.result ||
  1802. (a.type === '民政死亡比对'
  1803. ? `已登记继承人 ${data.heirName || '-'},完成死亡状态更新并停止发放`
  1804. : data.heirName
  1805. ? `已核实人员信息并登记继承人 ${data.heirName}`
  1806. : '已核实并处理人员信息')
  1807. a.remarks = data.remarks || a.remarks
  1808. a.fileUrls = data.fileUrls || a.fileUrls
  1809. const prev = Array.isArray(a.lifecycle) ? a.lifecycle : []
  1810. a.lifecycle = [
  1811. ...prev,
  1812. {
  1813. actionLabel: '关闭告警',
  1814. status: '已关闭',
  1815. operator: getAppConfig().roles.find((r) => r.key === getAppConfig().currentRole)?.label || '业务部门',
  1816. result: a.result,
  1817. remark: data.remarks,
  1818. fileUrls: data.fileUrls,
  1819. operateTime: nowStr(),
  1820. },
  1821. ]
  1822. // 同步更新关联人员档案(按原姓名+街镇+村居匹配)
  1823. const person = mockPeople.find(
  1824. (p) =>
  1825. (p.name === oldName || p.name === data.personName) &&
  1826. p.town === (data.town || a.town) &&
  1827. p.village === (data.village || a.village),
  1828. )
  1829. if (person) {
  1830. if (data.personName) person.name = data.personName
  1831. if (data.idCard) person.idCard = data.idCard
  1832. if (data.bankCard) person.bankCard = data.bankCard
  1833. if (data.bankName) person.bankName = data.bankName
  1834. if (data.socialInsuranceCode !== undefined) {
  1835. person.socialInsuranceCode = data.socialInsuranceCode
  1836. }
  1837. if (data.town) person.town = data.town
  1838. if (data.village) person.village = data.village
  1839. if (a.type === '民政死亡比对') person.status = '死亡丧葬'
  1840. if (String(a.type || '').includes('退养资格驳回')) {
  1841. person.status = '待提交资格'
  1842. person.bizStatus = 'DRAFT'
  1843. person.retireQualifyType = undefined
  1844. }
  1845. }
  1846. const role =
  1847. getAppConfig().roles.find((r) => r.key === getAppConfig().currentRole) ||
  1848. getAppConfig().roles[0]
  1849. mockLogs.unshift({
  1850. id: mockLogs.length ? Math.max(...mockLogs.map((l) => l.id)) + 1 : 1,
  1851. time: nowStr(),
  1852. user: role.label,
  1853. role: role.label,
  1854. module: '监控告警',
  1855. action: `处理告警 ${a.id}(${a.town}${a.village}·${a.personName}${data.heirName ? `,继承人${data.heirName}` : ''})`,
  1856. result: '成功(村/镇告警状态已同步)',
  1857. })
  1858. }
  1859. return delay({ ok: true })
  1860. }
  1861. return real.handleAlert(payload, result)
  1862. }
  1863. /* ========== 查询统计 ========== */
  1864. export function fetchReportBatches() {
  1865. if (useMock()) return delay([...mockVillageBatches])
  1866. return real.fetchReportBatches()
  1867. }
  1868. export function fetchWorkbenchStats(params?: {
  1869. payMonth?: string
  1870. town?: string
  1871. village?: string
  1872. months?: number
  1873. roleKey?: string
  1874. }) {
  1875. if (useMock()) {
  1876. return delay({
  1877. personnelCount: mockPeople.length,
  1878. withLandCount: mockPeople.filter((p) => p.retireQualifyType === '有土地退养').length,
  1879. withoutLandCount: mockPeople.filter((p) => p.retireQualifyType === '无土地有资格').length,
  1880. qualifiedCount: mockPeople.length,
  1881. monthAmount: 0,
  1882. townQualify: [],
  1883. villageCharts: [],
  1884. })
  1885. }
  1886. return real.fetchWorkbenchStats(params)
  1887. }
  1888. /* ========== 系统管理 ========== */
  1889. export function fetchAccounts() {
  1890. if (useMock()) return delay([...mockAccounts])
  1891. return real.fetchAccounts()
  1892. }
  1893. export function fetchOpLogs() {
  1894. if (useMock()) return delay([...mockLogs])
  1895. return real.fetchOpLogs()
  1896. }
  1897. export function fetchOpLogsPage(params?: {
  1898. pageNum?: number
  1899. pageSize?: number
  1900. operator?: string
  1901. operatorRole?: string
  1902. bizType?: string
  1903. action?: string
  1904. result?: string
  1905. operateTimeFrom?: number
  1906. operateTimeTo?: number
  1907. }) {
  1908. const pageNum = params?.pageNum || 1
  1909. const pageSize = params?.pageSize || 10
  1910. if (useMock()) {
  1911. let list = [...mockLogs]
  1912. const op = params?.operator?.trim()
  1913. if (op) list = list.filter((r) => r.user.includes(op) || r.role.includes(op))
  1914. if (params?.result) list = list.filter((r) => r.result.includes(params.result!))
  1915. if (params?.bizType) list = list.filter((r) => r.module.includes(params.bizType!))
  1916. if (params?.action) list = list.filter((r) => r.action.includes(params.action!))
  1917. const start = (pageNum - 1) * pageSize
  1918. return delay({
  1919. list: list.slice(start, start + pageSize),
  1920. total: list.length,
  1921. pageNum,
  1922. pageSize,
  1923. })
  1924. }
  1925. return real.fetchOpLogsPage(params)
  1926. }
  1927. export function factoryResetBusinessData() {
  1928. if (useMock()) return delay({ ok: true as const, destroyedTotal: 0, detail: {}, message: 'mock 跳过' })
  1929. return real.factoryResetBusinessData()
  1930. }
  1931. /** —— 系统管理员运维菜单 —— */
  1932. export function listRegionsAdmin(params?: { level?: string; parentId?: string }) {
  1933. if (useMock()) return delay([])
  1934. return real.listRegionsAdmin(params)
  1935. }
  1936. export function saveRegionAdmin(payload: {
  1937. id?: string
  1938. code: string
  1939. name: string
  1940. level: string
  1941. parentId?: string
  1942. entrustPlanCode?: string
  1943. defaultBank?: string
  1944. enabled?: boolean
  1945. remarks?: string
  1946. districtSharePercent?: number
  1947. townSharePercent?: number
  1948. }) {
  1949. if (useMock()) return delay({ ok: true as const })
  1950. return real.saveRegionAdmin(payload)
  1951. }
  1952. export function deleteRegionAdmin(id: string) {
  1953. if (useMock()) return delay({ ok: true as const })
  1954. return real.deleteRegionAdmin(id)
  1955. }
  1956. export function pageAdminBatches(params?: {
  1957. payMonth?: string
  1958. townId?: string
  1959. villageId?: string
  1960. batchLevel?: string
  1961. status?: string
  1962. pageNum?: number
  1963. pageSize?: number
  1964. }) {
  1965. if (useMock()) return delay({ list: [], total: 0, pageNum: 1, pageSize: 10 })
  1966. return real.pageAdminBatches(params)
  1967. }
  1968. export function purgeBatchAdmin(batchId: string) {
  1969. if (useMock()) return delay({ ok: true as const })
  1970. return real.purgeBatchAdmin(batchId)
  1971. }
  1972. export function deletePersonnelAdmin(personnelId: string) {
  1973. if (useMock()) return delay({ ok: true as const })
  1974. return real.deletePersonnelAdmin(personnelId)
  1975. }
  1976. export function deletePersonnelBatchAdmin(ids: string[]) {
  1977. if (useMock()) return delay({ ok: true as const, successCount: ids.length, failCount: 0, message: `已删除 ${ids.length} 人` })
  1978. return real.deletePersonnelBatchAdmin(ids)
  1979. }
  1980. export function clearAllPersonnelAdmin() {
  1981. if (useMock()) return delay({ ok: true as const, successCount: 0, failCount: 0, message: '已清空 0 人' })
  1982. return real.clearAllPersonnelAdmin()
  1983. }
  1984. export function updatePersonnelStatusAdmin(payload: {
  1985. personnelId: string
  1986. bizStatus: string
  1987. payThisMonth?: boolean
  1988. remarks?: string
  1989. }) {
  1990. if (useMock()) return delay({ ok: true as const })
  1991. return real.updatePersonnelStatusAdmin(payload)
  1992. }
  1993. export function initPersonnelStatusAdmin(payload: {
  1994. bizStatus: string
  1995. insuranceMatchResult: string
  1996. retireQualifyType?: string
  1997. payThisMonth?: boolean
  1998. townId?: string
  1999. villageId?: string
  2000. }) {
  2001. if (useMock()) {
  2002. return delay({
  2003. ok: true as const,
  2004. updatedCount: 0,
  2005. skippedArchived: 0,
  2006. skippedSame: 0,
  2007. failCount: 0,
  2008. message: '已初始化(演示)',
  2009. })
  2010. }
  2011. return real.initPersonnelStatusAdmin(payload)
  2012. }
  2013. /* ========== 核算 / 退款 / 报表 / 运维扩展 ========== */
  2014. export function snapshotCalcRound(batchId: string) {
  2015. if (useMock()) return delay({ ok: true })
  2016. return real.snapshotCalcRound(batchId)
  2017. }
  2018. export function listCalcRounds(batchId: string) {
  2019. if (useMock()) return delay([] as Record<string, unknown>[])
  2020. return real.listCalcRounds(batchId)
  2021. }
  2022. export function lockCalc(batchId: string, locked: boolean) {
  2023. if (useMock()) return delay({ ok: true, locked })
  2024. return real.lockCalc(batchId, locked)
  2025. }
  2026. export function advanceRefundProgress(id: string, stage: string) {
  2027. if (useMock()) return delay({ ok: true })
  2028. return real.advanceRefundProgress(id, stage)
  2029. }
  2030. export function fetchRefundStats() {
  2031. if (useMock()) return delay({ total: 0, byStage: {} })
  2032. return real.fetchRefundStats()
  2033. }
  2034. export function fetchReportTopic(
  2035. type: real.ReportTopicType,
  2036. params?: { townId?: string; payMonth?: string; bizType?: string },
  2037. ) {
  2038. if (useMock()) return delay([] as Record<string, unknown>[])
  2039. return real.fetchReportTopic(type, params)
  2040. }
  2041. export function exportReportSummary(payMonth?: string) {
  2042. if (useMock()) {
  2043. ElMessage.info('演示模式:请切换联调后导出')
  2044. return delay({ ok: false as const })
  2045. }
  2046. return real.exportReportSummary(payMonth)
  2047. }
  2048. export function fetchYoy(payMonth?: string) {
  2049. if (useMock()) return delay({} as Record<string, unknown>)
  2050. return real.fetchYoy(payMonth)
  2051. }
  2052. export function downloadStoredFile(fileUrl: string, fileName?: string) {
  2053. if (useMock()) {
  2054. ElMessage.info('演示模式:请切换联调后下载')
  2055. return delay(undefined)
  2056. }
  2057. return real.downloadStoredFile(fileUrl, fileName)
  2058. }
  2059. export function saveAccount(payload: {
  2060. username: string
  2061. password?: string
  2062. displayName?: string
  2063. role?: string
  2064. }) {
  2065. if (useMock()) return delay({ ok: true, id: String(Date.now()) })
  2066. return real.saveAccount(payload)
  2067. }
  2068. export function setAccountStatus(id: string, enabled: boolean) {
  2069. if (useMock()) return delay({ ok: true })
  2070. return real.setAccountStatus(id, enabled)
  2071. }
  2072. export function resetAccountPassword(id: string, password?: string) {
  2073. if (useMock()) return delay({ ok: true })
  2074. return real.resetAccountPassword(id, password)
  2075. }
  2076. export function fetchLoginLogs(limit = 100) {
  2077. if (useMock()) return delay([] as OpLog[])
  2078. return real.fetchLoginLogs(limit)
  2079. }
  2080. export function fetchLoginLogsPage(params?: { pageNum?: number; pageSize?: number }) {
  2081. const pageNum = params?.pageNum || 1
  2082. const pageSize = params?.pageSize || 10
  2083. if (useMock()) {
  2084. return delay({ list: [] as OpLog[], total: 0, pageNum, pageSize })
  2085. }
  2086. return real.fetchLoginLogsPage(params)
  2087. }
  2088. export function recordLoginAudit(payload: {
  2089. username?: string
  2090. roleKey?: string
  2091. roleLabel?: string
  2092. ip?: string
  2093. result?: string
  2094. }) {
  2095. if (useMock()) return delay({ ok: true as const })
  2096. return real.recordLoginAudit(payload)
  2097. }
  2098. export function exportProcessTimeline(params: { batchId?: string; personnelId?: string }) {
  2099. if (useMock()) {
  2100. ElMessage.info('演示模式:请切换联调后导出')
  2101. return delay({ ok: false as const })
  2102. }
  2103. return real.exportProcessTimeline(params)
  2104. }
  2105. export function fetchBatchProgress(batchId: string) {
  2106. if (useMock()) return delay([] as Array<Record<string, unknown>>)
  2107. return real.fetchBatchProgress(batchId)
  2108. }
  2109. export function exportOpLogs() {
  2110. if (useMock()) {
  2111. ElMessage.info('演示模式:请切换联调后导出')
  2112. return delay({ ok: false as const })
  2113. }
  2114. return real.exportOpLogs()
  2115. }
  2116. export function cleanupOpLogs(days = 90) {
  2117. if (useMock()) return delay({ ok: true, removed: 0, days })
  2118. return real.cleanupOpLogs(days)
  2119. }
  2120. export function fetchValidateRules() {
  2121. if (useMock()) {
  2122. return delay({
  2123. idCardValidate: true,
  2124. bankCardRequired: true,
  2125. deathStillPayScan: true,
  2126. })
  2127. }
  2128. return real.fetchValidateRules()
  2129. }
  2130. export function saveValidateRules(rules: Record<string, unknown>) {
  2131. if (useMock()) return delay({ ok: true })
  2132. return real.saveValidateRules(rules)
  2133. }
  2134. export function fetchStandardPendingTip(town?: string) {
  2135. if (useMock()) return delay('')
  2136. return real.fetchStandardPendingTip(town)
  2137. }