291 lines
9.4 KiB
TypeScript
291 lines
9.4 KiB
TypeScript
import type { CertRow } from '@/api/pl'
|
||
|
||
export const PL_PERSONNEL_CERT_MISSING_SUFFIX = '(证书缺失)'
|
||
|
||
export const PL_PERSONNEL_DRIVER_REQUIRED_CERT_NAMES = [
|
||
'身份证',
|
||
'驾驶证',
|
||
'驾驶员从业资格证'
|
||
] as const
|
||
|
||
export const PL_PERSONNEL_ESCORT_REQUIRED_CERT_NAMES = [
|
||
'身份证',
|
||
'押运员从业资格证'
|
||
] as const
|
||
|
||
export const PL_PERSONNEL_BOTH_REQUIRED_CERT_NAMES = [
|
||
'身份证',
|
||
'驾驶证',
|
||
'驾驶员从业资格证',
|
||
'押运员从业资格证'
|
||
] as const
|
||
|
||
export type PersonnelCertRole = 'driver' | 'escort'
|
||
export type PersonnelType = 'driver' | 'escort' | 'both'
|
||
|
||
export const PL_PERSONNEL_TYPE_OPTIONS = [
|
||
{ value: 'driver', label: '驾驶员' },
|
||
{ value: 'escort', label: '押运员' },
|
||
{ value: 'both', label: '驾驶员兼押运员' }
|
||
] as const
|
||
|
||
export type PersonnelAffiliation = 'self' | 'carrier'
|
||
|
||
export const PL_PERSONNEL_AFFILIATION_OPTIONS = [
|
||
{ value: 'self', label: '山峡' },
|
||
{ value: 'carrier', label: '非山峡' }
|
||
] as const
|
||
|
||
export function getPersonnelAffiliationLabel(affiliation?: string | null): string {
|
||
const found = PL_PERSONNEL_AFFILIATION_OPTIONS.find(item => item.value === affiliation)
|
||
return found?.label || affiliation || '—'
|
||
}
|
||
|
||
export function getPersonnelAffiliationTagType(
|
||
affiliation?: string | null
|
||
): 'success' | 'warning' | 'info' {
|
||
if (affiliation === 'self') return 'success'
|
||
if (affiliation === 'carrier') return 'warning'
|
||
return 'info'
|
||
}
|
||
|
||
export function validatePersonnelAffiliationVehicleCompat(
|
||
personnelAffiliation: string | undefined,
|
||
vehicleType: string | undefined,
|
||
onError: (message: string) => void
|
||
): boolean {
|
||
if (!personnelAffiliation || !vehicleType) return true
|
||
if (personnelAffiliation === vehicleType) return true
|
||
const vehicleLabel = vehicleType === 'self' ? '山峡' : '非山峡'
|
||
onError(`${vehicleLabel}车辆仅可关联${vehicleLabel}人员`)
|
||
return false
|
||
}
|
||
|
||
export function getPersonnelTypeLabel(type?: string | null): string {
|
||
const found = PL_PERSONNEL_TYPE_OPTIONS.find(item => item.value === type)
|
||
return found?.label || type || '—'
|
||
}
|
||
|
||
export function getPersonnelTypeTagType(type?: string | null): 'success' | 'warning' | 'primary' | 'info' {
|
||
if (type === 'driver') return 'primary'
|
||
if (type === 'escort') return 'warning'
|
||
if (type === 'both') return 'success'
|
||
return 'info'
|
||
}
|
||
|
||
export function getRequiredPersonnelCertNamesByType(
|
||
personnelType?: string | null
|
||
): readonly string[] {
|
||
if (personnelType === 'escort') {
|
||
return PL_PERSONNEL_ESCORT_REQUIRED_CERT_NAMES
|
||
}
|
||
if (personnelType === 'driver') {
|
||
return PL_PERSONNEL_DRIVER_REQUIRED_CERT_NAMES
|
||
}
|
||
return PL_PERSONNEL_BOTH_REQUIRED_CERT_NAMES
|
||
}
|
||
|
||
/** 车辆关联人员:从人员主数据类型映射为关联角色(司机/押运员) */
|
||
export function mapPersonnelTypeToVehicleRole(
|
||
personnelType?: string | null
|
||
): 'driver' | 'escort' | undefined {
|
||
if (personnelType === 'driver') return 'driver'
|
||
if (personnelType === 'escort') return 'escort'
|
||
if (personnelType === 'both') return 'driver'
|
||
return undefined
|
||
}
|
||
|
||
export function validatePersonnelTypeRoleCompat(
|
||
personnelType: string | undefined,
|
||
role: string | undefined,
|
||
onError: (message: string) => void
|
||
): boolean {
|
||
if (!personnelType || !role) return true
|
||
if (personnelType === 'driver' && role === 'escort') {
|
||
onError('驾驶员类型人员不可关联为押运员')
|
||
return false
|
||
}
|
||
if (personnelType === 'escort' && role === 'driver') {
|
||
onError('押运员类型人员不可关联为驾驶员')
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
export function isPersonnelCertStrictComplete(
|
||
cert: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls'> | undefined,
|
||
requiredName: string
|
||
): boolean {
|
||
if (!cert?.certName?.trim()) return false
|
||
if (!matchRequiredCertName(cert.certName, requiredName)) return false
|
||
if (!cert.expireDate) return false
|
||
// 证书附件非必填
|
||
return true
|
||
}
|
||
|
||
export function matchRequiredCertName(
|
||
actualName: string | undefined,
|
||
requiredName: string
|
||
): boolean {
|
||
const name = actualName?.trim() || ''
|
||
if (!name) return false
|
||
if (requiredName === '押运员从业资格证') {
|
||
return name === '押运员从业资格证' || name === '押运员证'
|
||
}
|
||
return name === requiredName
|
||
}
|
||
|
||
export function isPersonnelCertUploaded(cert?: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls'>) {
|
||
return Boolean(cert?.certName?.trim() && cert?.expireDate)
|
||
}
|
||
|
||
export function getRequiredPersonnelCertNames(role: PersonnelCertRole): readonly string[] {
|
||
return role === 'driver'
|
||
? PL_PERSONNEL_DRIVER_REQUIRED_CERT_NAMES
|
||
: PL_PERSONNEL_ESCORT_REQUIRED_CERT_NAMES
|
||
}
|
||
|
||
export function getMissingPersonnelCertNames(
|
||
role: PersonnelCertRole,
|
||
certList: CertRow[] = []
|
||
): string[] {
|
||
return getRequiredPersonnelCertNames(role).filter(requiredName => {
|
||
const matched = certList.find(cert => matchRequiredCertName(cert.certName, requiredName))
|
||
return !isPersonnelCertUploaded(matched)
|
||
})
|
||
}
|
||
|
||
export function hasPersonnelCertMissing(
|
||
role: PersonnelCertRole,
|
||
certList: CertRow[] = []
|
||
): boolean {
|
||
return getMissingPersonnelCertNames(role, certList).length > 0
|
||
}
|
||
|
||
/** 人员主数据页:按人员类型检查必备证书 */
|
||
export function hasPersonnelMasterCertMissing(
|
||
certList: CertRow[] = [],
|
||
personnelType?: string | null
|
||
): boolean {
|
||
const required = getRequiredPersonnelCertNamesByType(personnelType || 'both')
|
||
return required.some(requiredName => {
|
||
const matched = certList.find(cert => matchRequiredCertName(cert.certName, requiredName))
|
||
return !isPersonnelCertStrictComplete(matched, requiredName)
|
||
})
|
||
}
|
||
|
||
export function getPersonnelCertMissingSuffix(
|
||
certList: CertRow[] = [],
|
||
role?: PersonnelCertRole,
|
||
personnelType?: string | null
|
||
): string {
|
||
if (personnelType) {
|
||
return hasPersonnelMasterCertMissing(certList, personnelType)
|
||
? PL_PERSONNEL_CERT_MISSING_SUFFIX
|
||
: ''
|
||
}
|
||
if (role) {
|
||
return hasPersonnelCertMissing(role, certList) ? PL_PERSONNEL_CERT_MISSING_SUFFIX : ''
|
||
}
|
||
return hasPersonnelMasterCertMissing(certList) ? PL_PERSONNEL_CERT_MISSING_SUFFIX : ''
|
||
}
|
||
|
||
export const PL_PERSONNEL_CERT_SHORT_LABELS: Record<string, string> = {
|
||
身份证: '身',
|
||
驾驶证: '驾',
|
||
驾驶员从业资格证: '业',
|
||
押运员从业资格证: '押'
|
||
}
|
||
|
||
export type PersonnelCertBadgeStatus = 'missing' | 'valid' | 'expiring' | 'expired'
|
||
|
||
export interface PersonnelCertBadgeItem {
|
||
requiredName: string
|
||
shortLabel: string
|
||
status: PersonnelCertBadgeStatus
|
||
title: string
|
||
}
|
||
|
||
function getPersonnelCertMissingFieldLabels(
|
||
cert: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls'> | undefined,
|
||
requiredName: string
|
||
): string[] {
|
||
if (!cert || !matchRequiredCertName(cert.certName, requiredName)) {
|
||
return ['有效期']
|
||
}
|
||
const missing: string[] = []
|
||
if (!cert.expireDate) missing.push('有效期')
|
||
// 证书附件非必填
|
||
return missing
|
||
}
|
||
|
||
function formatPersonnelCertBadgeTooltip(
|
||
requiredName: string,
|
||
status: PersonnelCertBadgeStatus,
|
||
cert?: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls' | 'remindDays'>
|
||
): string {
|
||
if (status === 'missing') {
|
||
if (!cert || !matchRequiredCertName(cert.certName, requiredName)) {
|
||
return `${requiredName}:缺失(未登记该证书)`
|
||
}
|
||
const missing = getPersonnelCertMissingFieldLabels(cert, requiredName)
|
||
return `${requiredName}:缺失(缺少${missing.join('、')})`
|
||
}
|
||
|
||
const expireDate = cert?.expireDate || '—'
|
||
const expDate = new Date(expireDate)
|
||
const today = new Date()
|
||
today.setHours(0, 0, 0, 0)
|
||
expDate.setHours(0, 0, 0, 0)
|
||
const diffDays = Math.ceil((expDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
|
||
const certNo = cert?.certNo?.trim()
|
||
const certNoText = certNo ? `,证号 ${certNo}` : ''
|
||
|
||
if (status === 'expired') {
|
||
return `${requiredName}:已过期,有效期 ${expireDate},已过期 ${Math.abs(diffDays)} 天${certNoText}`
|
||
}
|
||
if (status === 'expiring') {
|
||
return `${requiredName}:临期,有效期 ${expireDate},剩余 ${diffDays} 天${certNoText}`
|
||
}
|
||
return `${requiredName}:正常,有效期 ${expireDate}${certNoText}`
|
||
}
|
||
|
||
export function getPersonnelCertBadgeStatus(
|
||
cert: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls' | 'remindDays'> | undefined,
|
||
requiredName: string
|
||
): PersonnelCertBadgeStatus {
|
||
if (!isPersonnelCertStrictComplete(cert, requiredName)) {
|
||
return 'missing'
|
||
}
|
||
const expireDate = cert?.expireDate
|
||
if (!expireDate) return 'missing'
|
||
|
||
const remindDays = cert?.remindDays ?? 30
|
||
const expDate = new Date(expireDate)
|
||
const today = new Date()
|
||
today.setHours(0, 0, 0, 0)
|
||
expDate.setHours(0, 0, 0, 0)
|
||
const diffDays = Math.ceil((expDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
|
||
|
||
if (diffDays <= 0) return 'expired'
|
||
if (diffDays <= remindDays) return 'expiring'
|
||
return 'valid'
|
||
}
|
||
|
||
export function getPersonnelRequiredCertBadges(
|
||
personnelType: string | null | undefined,
|
||
certList: CertRow[] = []
|
||
): PersonnelCertBadgeItem[] {
|
||
return getRequiredPersonnelCertNamesByType(personnelType).map(requiredName => {
|
||
const matched = certList.find(cert => matchRequiredCertName(cert.certName, requiredName))
|
||
const status = getPersonnelCertBadgeStatus(matched, requiredName)
|
||
const shortLabel = PL_PERSONNEL_CERT_SHORT_LABELS[requiredName] || requiredName.slice(0, 1)
|
||
return {
|
||
requiredName,
|
||
shortLabel,
|
||
status,
|
||
title: formatPersonnelCertBadgeTooltip(requiredName, status, matched)
|
||
}
|
||
})
|
||
}
|