pl-personnel-cert.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import type { CertRow } from '@/api/pl'
  2. export const PL_PERSONNEL_CERT_MISSING_SUFFIX = '(证书缺失)'
  3. export const PL_PERSONNEL_DRIVER_REQUIRED_CERT_NAMES = [
  4. '身份证',
  5. '驾驶证',
  6. '驾驶员从业资格证'
  7. ] as const
  8. export const PL_PERSONNEL_ESCORT_REQUIRED_CERT_NAMES = [
  9. '身份证',
  10. '押运员从业资格证'
  11. ] as const
  12. export const PL_PERSONNEL_BOTH_REQUIRED_CERT_NAMES = [
  13. '身份证',
  14. '驾驶证',
  15. '驾驶员从业资格证',
  16. '押运员从业资格证'
  17. ] as const
  18. export type PersonnelCertRole = 'driver' | 'escort'
  19. export type PersonnelType = 'driver' | 'escort' | 'both'
  20. export const PL_PERSONNEL_TYPE_OPTIONS = [
  21. { value: 'driver', label: '驾驶员' },
  22. { value: 'escort', label: '押运员' },
  23. { value: 'both', label: '驾驶员兼押运员' }
  24. ] as const
  25. export type PersonnelAffiliation = 'self' | 'carrier'
  26. export const PL_PERSONNEL_AFFILIATION_OPTIONS = [
  27. { value: 'self', label: '山峡' },
  28. { value: 'carrier', label: '非山峡' }
  29. ] as const
  30. export function getPersonnelAffiliationLabel(affiliation?: string | null): string {
  31. const found = PL_PERSONNEL_AFFILIATION_OPTIONS.find(item => item.value === affiliation)
  32. return found?.label || affiliation || '—'
  33. }
  34. export function getPersonnelAffiliationTagType(
  35. affiliation?: string | null
  36. ): 'success' | 'warning' | 'info' {
  37. if (affiliation === 'self') return 'success'
  38. if (affiliation === 'carrier') return 'warning'
  39. return 'info'
  40. }
  41. export function validatePersonnelAffiliationVehicleCompat(
  42. personnelAffiliation: string | undefined,
  43. vehicleType: string | undefined,
  44. onError: (message: string) => void
  45. ): boolean {
  46. if (!personnelAffiliation || !vehicleType) return true
  47. if (personnelAffiliation === vehicleType) return true
  48. const vehicleLabel = vehicleType === 'self' ? '山峡' : '非山峡'
  49. onError(`${vehicleLabel}车辆仅可关联${vehicleLabel}人员`)
  50. return false
  51. }
  52. export function getPersonnelTypeLabel(type?: string | null): string {
  53. const found = PL_PERSONNEL_TYPE_OPTIONS.find(item => item.value === type)
  54. return found?.label || type || '—'
  55. }
  56. export function getPersonnelTypeTagType(type?: string | null): 'success' | 'warning' | 'primary' | 'info' {
  57. if (type === 'driver') return 'primary'
  58. if (type === 'escort') return 'warning'
  59. if (type === 'both') return 'success'
  60. return 'info'
  61. }
  62. export function getRequiredPersonnelCertNamesByType(
  63. personnelType?: string | null
  64. ): readonly string[] {
  65. if (personnelType === 'escort') {
  66. return PL_PERSONNEL_ESCORT_REQUIRED_CERT_NAMES
  67. }
  68. if (personnelType === 'driver') {
  69. return PL_PERSONNEL_DRIVER_REQUIRED_CERT_NAMES
  70. }
  71. return PL_PERSONNEL_BOTH_REQUIRED_CERT_NAMES
  72. }
  73. /** 车辆关联人员:从人员主数据类型映射为关联角色(司机/押运员) */
  74. export function mapPersonnelTypeToVehicleRole(
  75. personnelType?: string | null
  76. ): 'driver' | 'escort' | undefined {
  77. if (personnelType === 'driver') return 'driver'
  78. if (personnelType === 'escort') return 'escort'
  79. if (personnelType === 'both') return 'driver'
  80. return undefined
  81. }
  82. export function validatePersonnelTypeRoleCompat(
  83. personnelType: string | undefined,
  84. role: string | undefined,
  85. onError: (message: string) => void
  86. ): boolean {
  87. if (!personnelType || !role) return true
  88. if (personnelType === 'driver' && role === 'escort') {
  89. onError('驾驶员类型人员不可关联为押运员')
  90. return false
  91. }
  92. if (personnelType === 'escort' && role === 'driver') {
  93. onError('押运员类型人员不可关联为驾驶员')
  94. return false
  95. }
  96. return true
  97. }
  98. export function isPersonnelCertStrictComplete(
  99. cert: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls'> | undefined,
  100. requiredName: string
  101. ): boolean {
  102. if (!cert?.certName?.trim()) return false
  103. if (!matchRequiredCertName(cert.certName, requiredName)) return false
  104. if (!cert.expireDate) return false
  105. // 证书附件非必填
  106. return true
  107. }
  108. export function matchRequiredCertName(
  109. actualName: string | undefined,
  110. requiredName: string
  111. ): boolean {
  112. const name = actualName?.trim() || ''
  113. if (!name) return false
  114. if (requiredName === '押运员从业资格证') {
  115. return name === '押运员从业资格证' || name === '押运员证'
  116. }
  117. return name === requiredName
  118. }
  119. export function isPersonnelCertUploaded(cert?: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls'>) {
  120. return Boolean(cert?.certName?.trim() && cert?.expireDate)
  121. }
  122. export function getRequiredPersonnelCertNames(role: PersonnelCertRole): readonly string[] {
  123. return role === 'driver'
  124. ? PL_PERSONNEL_DRIVER_REQUIRED_CERT_NAMES
  125. : PL_PERSONNEL_ESCORT_REQUIRED_CERT_NAMES
  126. }
  127. export function getMissingPersonnelCertNames(
  128. role: PersonnelCertRole,
  129. certList: CertRow[] = []
  130. ): string[] {
  131. return getRequiredPersonnelCertNames(role).filter(requiredName => {
  132. const matched = certList.find(cert => matchRequiredCertName(cert.certName, requiredName))
  133. return !isPersonnelCertUploaded(matched)
  134. })
  135. }
  136. export function hasPersonnelCertMissing(
  137. role: PersonnelCertRole,
  138. certList: CertRow[] = []
  139. ): boolean {
  140. return getMissingPersonnelCertNames(role, certList).length > 0
  141. }
  142. /** 人员主数据页:按人员类型检查必备证书 */
  143. export function hasPersonnelMasterCertMissing(
  144. certList: CertRow[] = [],
  145. personnelType?: string | null
  146. ): boolean {
  147. const required = getRequiredPersonnelCertNamesByType(personnelType || 'both')
  148. return required.some(requiredName => {
  149. const matched = certList.find(cert => matchRequiredCertName(cert.certName, requiredName))
  150. return !isPersonnelCertStrictComplete(matched, requiredName)
  151. })
  152. }
  153. export function getPersonnelCertMissingSuffix(
  154. certList: CertRow[] = [],
  155. role?: PersonnelCertRole,
  156. personnelType?: string | null
  157. ): string {
  158. if (personnelType) {
  159. return hasPersonnelMasterCertMissing(certList, personnelType)
  160. ? PL_PERSONNEL_CERT_MISSING_SUFFIX
  161. : ''
  162. }
  163. if (role) {
  164. return hasPersonnelCertMissing(role, certList) ? PL_PERSONNEL_CERT_MISSING_SUFFIX : ''
  165. }
  166. return hasPersonnelMasterCertMissing(certList) ? PL_PERSONNEL_CERT_MISSING_SUFFIX : ''
  167. }
  168. export const PL_PERSONNEL_CERT_SHORT_LABELS: Record<string, string> = {
  169. 身份证: '身',
  170. 驾驶证: '驾',
  171. 驾驶员从业资格证: '业',
  172. 押运员从业资格证: '押'
  173. }
  174. export type PersonnelCertBadgeStatus = 'missing' | 'valid' | 'expiring' | 'expired'
  175. export interface PersonnelCertBadgeItem {
  176. requiredName: string
  177. shortLabel: string
  178. status: PersonnelCertBadgeStatus
  179. title: string
  180. }
  181. function getPersonnelCertMissingFieldLabels(
  182. cert: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls'> | undefined,
  183. requiredName: string
  184. ): string[] {
  185. if (!cert || !matchRequiredCertName(cert.certName, requiredName)) {
  186. return ['有效期']
  187. }
  188. const missing: string[] = []
  189. if (!cert.expireDate) missing.push('有效期')
  190. // 证书附件非必填
  191. return missing
  192. }
  193. function formatPersonnelCertBadgeTooltip(
  194. requiredName: string,
  195. status: PersonnelCertBadgeStatus,
  196. cert?: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls' | 'remindDays'>
  197. ): string {
  198. if (status === 'missing') {
  199. if (!cert || !matchRequiredCertName(cert.certName, requiredName)) {
  200. return `${requiredName}:缺失(未登记该证书)`
  201. }
  202. const missing = getPersonnelCertMissingFieldLabels(cert, requiredName)
  203. return `${requiredName}:缺失(缺少${missing.join('、')})`
  204. }
  205. const expireDate = cert?.expireDate || '—'
  206. const expDate = new Date(expireDate)
  207. const today = new Date()
  208. today.setHours(0, 0, 0, 0)
  209. expDate.setHours(0, 0, 0, 0)
  210. const diffDays = Math.ceil((expDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
  211. const certNo = cert?.certNo?.trim()
  212. const certNoText = certNo ? `,证号 ${certNo}` : ''
  213. if (status === 'expired') {
  214. return `${requiredName}:已过期,有效期 ${expireDate},已过期 ${Math.abs(diffDays)} 天${certNoText}`
  215. }
  216. if (status === 'expiring') {
  217. return `${requiredName}:临期,有效期 ${expireDate},剩余 ${diffDays} 天${certNoText}`
  218. }
  219. return `${requiredName}:正常,有效期 ${expireDate}${certNoText}`
  220. }
  221. export function getPersonnelCertBadgeStatus(
  222. cert: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls' | 'remindDays'> | undefined,
  223. requiredName: string
  224. ): PersonnelCertBadgeStatus {
  225. if (!isPersonnelCertStrictComplete(cert, requiredName)) {
  226. return 'missing'
  227. }
  228. const expireDate = cert?.expireDate
  229. if (!expireDate) return 'missing'
  230. const remindDays = cert?.remindDays ?? 30
  231. const expDate = new Date(expireDate)
  232. const today = new Date()
  233. today.setHours(0, 0, 0, 0)
  234. expDate.setHours(0, 0, 0, 0)
  235. const diffDays = Math.ceil((expDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
  236. if (diffDays <= 0) return 'expired'
  237. if (diffDays <= remindDays) return 'expiring'
  238. return 'valid'
  239. }
  240. export function getPersonnelRequiredCertBadges(
  241. personnelType: string | null | undefined,
  242. certList: CertRow[] = []
  243. ): PersonnelCertBadgeItem[] {
  244. return getRequiredPersonnelCertNamesByType(personnelType).map(requiredName => {
  245. const matched = certList.find(cert => matchRequiredCertName(cert.certName, requiredName))
  246. const status = getPersonnelCertBadgeStatus(matched, requiredName)
  247. const shortLabel = PL_PERSONNEL_CERT_SHORT_LABELS[requiredName] || requiredName.slice(0, 1)
  248. return {
  249. requiredName,
  250. shortLabel,
  251. status,
  252. title: formatPersonnelCertBadgeTooltip(requiredName, status, matched)
  253. }
  254. })
  255. }