import type { TransformList, TransformRecord } from '#/api'; /** 岗位人员资质(接口就绪后替换为真实请求) */ export namespace PersonnelQualificationModel { export type QualificationStatus = 'valid' | 'expiring' | 'expired'; export interface CertificateAttachment { id: string; name: string; url: string; type: 'image' | 'pdf'; } export interface Certificate { id: string; name: string; type?: string; number: string; expiryDate: string; longTerm?: boolean; status: QualificationStatus; attachments: CertificateAttachment[]; } export type OrganizationType = 'enterprise' | 'medicalInstitution'; export interface Personnel extends TransformRecord { organizationType?: OrganizationType; enterpriseId?: string; enterpriseName: string; decoctionCenterId?: string; decoctionCenterName: string; name: string; positions: string[]; employeeNo: string; idNumber: string; certificateNames: string; qualificationStatus: QualificationStatus; certificates: Certificate[]; } export interface PersonnelForm { id?: string; decoctionCenterId: string; decoctionCenterName?: string; name: string; positions: string[]; employeeNo: string; idNumber: string; certificates: Certificate[]; } export interface ListQuery { organizationType?: OrganizationType; organizationId?: string; enterpriseId?: string; decoctionCenterId?: string; position?: string; qualificationStatus?: QualificationStatus; keyword?: string; } export interface ExpirySummary { expiredCount: number; expiringCount: number; } } export const POSITION_OPTIONS = [ { label: '接方、审方', value: '接方、审方' }, { label: '审方', value: '审方' }, { label: '配药', value: '配药' }, { label: '复核', value: '复核' }, { label: '煎煮', value: '煎煮' }, { label: '打包', value: '打包' }, ]; export const CERTIFICATE_NAME_OPTIONS = [ { label: '健康证', value: '健康证' }, { label: '中药调剂员', value: '中药调剂员' }, { label: '中药保管员', value: '中药保管员' }, { label: '执业中药师', value: '执业中药师' }, ]; export const QUALIFICATION_STATUS_OPTIONS = [ { label: '有效', value: 'valid' }, { label: '即将过期', value: 'expiring' }, { label: '过期', value: 'expired' }, ]; export const ORGANIZATION_TYPE_OPTIONS: Array<{ label: string; value: PersonnelQualificationModel.OrganizationType; }> = [ { label: '煎药企业', value: 'enterprise' }, { label: '医疗机构', value: 'medicalInstitution' }, ]; export const QUALIFICATION_STATUS_LABELS: Record< PersonnelQualificationModel.QualificationStatus, string > = { valid: '有效', expiring: '即将过期', expired: '过期', }; /** 各证书类型独立的 mock 附件,避免多证共用同一套图片 */ function getDefaultAttachments( certName: string, index: number, ): PersonnelQualificationModel.CertificateAttachment[] { const attachmentSets: Record< string, PersonnelQualificationModel.CertificateAttachment[] > = { 健康证: [ { id: `health-${index}-1`, name: '健康证正面', url: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-8.jpeg', type: 'image', }, { id: `health-${index}-2`, name: '健康证反面', url: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-7.jpeg', type: 'image', }, ], 中药保管员: [ { id: `custodian-${index}-1`, name: '保管员资格证', url: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-6.jpeg', type: 'image', }, { id: `custodian-${index}-2`, name: '保管员证书扫描件', url: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-5.jpeg', type: 'image', }, ], 执业中药师: [ { id: `pharmacist-${index}-1`, name: '执业药师资格证', url: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-4.jpeg', type: 'image', }, { id: `pharmacist-${index}-2`, name: '执业药师注册证', url: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-3.jpeg', type: 'pdf', }, ], 中药调剂员: [ { id: `dispenser-${index}-1`, name: '调剂员资格证', url: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-2.jpeg', type: 'image', }, { id: `dispenser-${index}-2`, name: '调剂员证书扫描件', url: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg', type: 'image', }, ], }; return ( attachmentSets[certName] ?? [ { id: `cert-${index}-1`, name: '证书附件', url: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-8.jpeg', type: 'image', }, ] ); } function buildCertificates( items: Array< Omit & { attachments?: PersonnelQualificationModel.CertificateAttachment[]; } >, ): PersonnelQualificationModel.Certificate[] { return items.map((item, index) => ({ ...item, id: `cert-${index + 1}`, attachments: item.attachments ?? getDefaultAttachments(item.name, index), })); } function resolveWorstStatus( certificates: PersonnelQualificationModel.Certificate[], ): PersonnelQualificationModel.QualificationStatus { if (certificates.some((item) => item.status === 'expired')) return 'expired'; if (certificates.some((item) => item.status === 'expiring')) return 'expiring'; return 'valid'; } function buildPersonnel( data: Omit< PersonnelQualificationModel.Personnel, 'certificateNames' | 'qualificationStatus' >, ): PersonnelQualificationModel.Personnel { const certificateNames = data.certificates.map((item) => item.name).join('、'); return { ...data, certificateNames, qualificationStatus: resolveWorstStatus(data.certificates), }; } const MOCK_PERSONNEL: PersonnelQualificationModel.Personnel[] = [ buildPersonnel({ id: '1', organizationType: 'enterprise', enterpriseId: 'e1', enterpriseName: '重药煎药中心', decoctionCenterId: 'c1', decoctionCenterName: '重药华东煎药中心2', name: '孙明1', positions: ['接方、审方'], employeeNo: '28473', idNumber: '330102199001012839', certificates: buildCertificates([ { name: '健康证', number: '944895756806594342', expiryDate: '2027-3-20', status: 'valid', type: '健康证', }, { name: '中药保管员', number: '944895756806594343', expiryDate: '2026-4-15', status: 'expiring', type: '保管员', }, { name: '执业中药师', number: '944895756806594344', expiryDate: '2028-6-30', status: 'valid', type: '执业药师', }, ]), }), buildPersonnel({ id: '2', enterpriseId: 'e1', enterpriseName: '重药煎药中心', decoctionCenterId: 'c1', decoctionCenterName: '重药华东煎药中心2', name: '孙明2', positions: ['审方'], employeeNo: '28474', idNumber: '330103198802022840', certificates: buildCertificates([ { name: '健康证', number: '944895756806594345', expiryDate: '2025-1-10', status: 'expired', }, { name: '执业中药师', number: '944895756806594346', expiryDate: '2027-8-20', status: 'valid', }, ]), }), buildPersonnel({ id: '3', enterpriseId: 'e1', enterpriseName: '重药煎药中心', decoctionCenterId: 'c1', decoctionCenterName: '重药华东煎药中心2', name: '孙明3', positions: ['配药'], employeeNo: '28475', idNumber: '330104199203033841', certificates: buildCertificates([ { name: '中药调剂员', number: '944895756806594347', expiryDate: '2026-5-1', status: 'expiring', }, { name: '健康证', number: '944895756806594348', expiryDate: '2027-2-18', status: 'valid', }, ]), }), buildPersonnel({ id: '4', enterpriseId: 'e1', enterpriseName: '重药煎药中心', decoctionCenterId: 'c1', decoctionCenterName: '重药华东煎药中心2', name: '孙明4', positions: ['复核'], employeeNo: '28476', idNumber: '330105199504044842', certificates: buildCertificates([ { name: '健康证', number: '944895756806594349', expiryDate: '2027-12-31', status: 'valid', }, ]), }), buildPersonnel({ id: '5', enterpriseId: 'e1', enterpriseName: '重药煎药中心', decoctionCenterId: 'c1', decoctionCenterName: '重药华东煎药中心2', name: '孙明5', positions: ['煎煮'], employeeNo: '28477', idNumber: '330106199605055843', certificates: buildCertificates([ { name: '健康证', number: '944895756806594350', expiryDate: '2024-12-1', status: 'expired', }, ]), }), buildPersonnel({ id: '6', enterpriseId: 'e1', enterpriseName: '重药煎药中心', decoctionCenterId: 'c1', decoctionCenterName: '重药华东煎药中心2', name: '孙明6', positions: ['打包'], employeeNo: '28478', idNumber: '330107199706066844', certificates: buildCertificates([ { name: '健康证', number: '944895756806594351', expiryDate: '2027-9-10', status: 'valid', }, ]), }), buildPersonnel({ id: '7', enterpriseId: 'e2', enterpriseName: '杭州中药煎配中心', decoctionCenterId: 'c2', decoctionCenterName: '西湖煎药中心', name: '李明1', positions: ['接方、审方'], employeeNo: '38473', idNumber: '330108199807077845', certificates: buildCertificates([ { name: '执业中药师', number: '944895756806594352', expiryDate: '2026-3-20', status: 'expiring', }, ]), }), buildPersonnel({ id: '8', organizationType: 'enterprise', enterpriseId: 'e2', enterpriseName: '杭州中药煎配中心', decoctionCenterId: 'c2', decoctionCenterName: '西湖煎药中心', name: '李明2', positions: ['配药'], employeeNo: '38474', idNumber: '330109199908088846', certificates: buildCertificates([ { name: '中药调剂员', number: '944895756806594353', expiryDate: '2027-6-15', status: 'valid', }, ]), }), buildPersonnel({ id: '9', organizationType: 'medicalInstitution', enterpriseId: 'm1', enterpriseName: '蒋村社区卫生服务中心', decoctionCenterId: 'c3', decoctionCenterName: '蒋村煎药中心', name: '王芳', positions: ['接方、审方'], employeeNo: '48473', idNumber: '330110199001011234', certificates: buildCertificates([ { name: '健康证', number: '944895756806594360', expiryDate: '2027-5-20', status: 'valid', }, { name: '执业中药师', number: '944895756806594361', expiryDate: '2026-2-10', status: 'expiring', }, ]), }), buildPersonnel({ id: '10', organizationType: 'medicalInstitution', enterpriseId: 'm2', enterpriseName: '西湖区中医院', decoctionCenterId: 'c4', decoctionCenterName: '西湖区中医院煎药室', name: '赵强', positions: ['配药'], employeeNo: '48474', idNumber: '330111198803022345', certificates: buildCertificates([ { name: '中药调剂员', number: '944895756806594362', expiryDate: '2027-8-1', status: 'valid', }, ]), }), ]; let mockStore = [...MOCK_PERSONNEL]; function filterPersonnel( list: PersonnelQualificationModel.Personnel[], query?: PersonnelQualificationModel.ListQuery, ) { if (!query) return list; return list.filter((item) => { const organizationId = query.organizationId ?? query.enterpriseId; const organizationType = query.organizationType ?? 'enterprise'; if (organizationId) { const itemType = item.organizationType ?? 'enterprise'; if (itemType !== organizationType) { return false; } if (item.enterpriseId !== organizationId) { return false; } } if ( query.decoctionCenterId && item.decoctionCenterId !== query.decoctionCenterId ) { return false; } if (query.position && !item.positions.includes(query.position)) { return false; } if ( query.qualificationStatus && item.qualificationStatus !== query.qualificationStatus ) { return false; } if (query.keyword) { const keyword = query.keyword.trim(); if ( !item.name.includes(keyword) && !item.employeeNo.includes(keyword) ) { return false; } } return true; }); } function computeExpirySummary(): PersonnelQualificationModel.ExpirySummary { let expiredCount = 0; let expiringCount = 0; for (const person of mockStore) { if (person.qualificationStatus === 'expired') expiredCount += 1; else if (person.qualificationStatus === 'expiring') expiringCount += 1; } return { expiredCount, expiringCount }; } /** 岗位人员资质列表(当前为本地 mock,后期对接后端接口) */ export function listPersonnelQualificationsMethod( page = 1, size = 10, query?: PersonnelQualificationModel.ListQuery, ): Promise> { const filtered = filterPersonnel(mockStore, query); const start = (page - 1) * size; const items = filtered.slice(start, start + size); return Promise.resolve({ items, total: filtered.length, data: { page, size, total: filtered.length }, }); } /** 资质到期统计(当前为本地 mock) */ export function getPersonnelQualificationSummaryMethod(): Promise { return Promise.resolve(computeExpirySummary()); } /** 岗位人员资质详情(当前为本地 mock) */ export function getPersonnelQualificationMethod(id: string) { const item = mockStore.find((row) => row.id === id); if (!item) { return Promise.reject(new Error('人员记录不存在')); } return Promise.resolve({ ...item }); } /** 新增/修改岗位人员资质(当前为本地 mock) */ export function editPersonnelQualificationMethod( data: PersonnelQualificationModel.PersonnelForm, ) { const certificates = data.certificates.map((cert, index) => ({ ...cert, id: cert.id || `cert-${Date.now()}-${index}`, status: cert.status ?? 'valid', attachments: cert.attachments ?? [], })); const enterpriseName = mockStore.find((item) => item.decoctionCenterId === data.decoctionCenterId) ?.enterpriseName ?? '重药煎药中心'; const decoctionCenterName = data.decoctionCenterName ?? mockStore.find((item) => item.decoctionCenterId === data.decoctionCenterId) ?.decoctionCenterName ?? '重药华东煎药中心2'; const payload = buildPersonnel({ id: data.id ?? String(Date.now()), enterpriseId: mockStore.find( (item) => item.decoctionCenterId === data.decoctionCenterId, )?.enterpriseId, enterpriseName, decoctionCenterId: data.decoctionCenterId, decoctionCenterName, name: data.name, positions: data.positions, employeeNo: data.employeeNo, idNumber: data.idNumber, certificates, }); if (data.id) { const index = mockStore.findIndex((item) => item.id === data.id); if (index === -1) { return Promise.reject(new Error('人员记录不存在')); } mockStore[index] = payload; } else { mockStore = [payload, ...mockStore]; } return Promise.resolve(payload); } /** 删除岗位人员资质(当前为本地 mock) */ export function deletePersonnelQualificationMethod(id: string) { const index = mockStore.findIndex((item) => item.id === id); if (index === -1) { return Promise.reject(new Error('人员记录不存在')); } mockStore.splice(index, 1); return Promise.resolve(true); } /** 煎药企业选项(mock) */ export function optionsPersonnelEnterpriseMethod() { const map = new Map(); for (const item of mockStore) { if ((item.organizationType ?? 'enterprise') !== 'enterprise') continue; if (item.enterpriseId) { map.set(item.enterpriseId, item.enterpriseName); } } return Promise.resolve( [...map.entries()].map(([value, label]) => ({ label, value })), ); } /** 医疗机构选项(mock) */ export function optionsPersonnelMedicalInstitutionMethod() { const map = new Map(); for (const item of mockStore) { if (item.organizationType !== 'medicalInstitution') continue; if (item.enterpriseId) { map.set(item.enterpriseId, item.enterpriseName); } } return Promise.resolve( [...map.entries()].map(([value, label]) => ({ label, value })), ); } /** 煎药中心选项(mock) */ export function optionsPersonnelDecoctionCenterMethod( organizationId?: string, organizationType: PersonnelQualificationModel.OrganizationType = 'enterprise', ) { const map = new Map(); for (const item of mockStore) { const itemType = item.organizationType ?? 'enterprise'; if (itemType !== organizationType) continue; if (organizationId && item.enterpriseId !== organizationId) continue; if (item.decoctionCenterId) { map.set(item.decoctionCenterId, item.decoctionCenterName); } } return Promise.resolve( [...map.entries()].map(([value, label]) => ({ label, value })), ); }