108 lines
4.5 KiB
TypeScript
108 lines
4.5 KiB
TypeScript
// 负责人在职状态与离职接管链的共享口径(2026-10-12 用户确认)。
|
||
//
|
||
// 全量档案的运营负责人/设计师在职状态是「显示层」能力:库里永远存原始花名,
|
||
// 页面与导出按人员信息表(SupplyChainManagementOwnerConfig)实时判断:
|
||
// - 负责人在职 → 原样显示;
|
||
// - 负责人离职 → `花名(已离职)→ 接管人`,接管人依次取其部门现任负责人、
|
||
// 运营总负责人;接管人也离职时仅追加(已离职)标注,不再上抛;
|
||
// - 花名匹配不到人员信息 → 原样显示,状态「未匹配」(需要补人员信息或改档案);
|
||
// - 档案无负责人 → 状态「未设置」。
|
||
// 设计师离职只加(已离职)标注,不做接管(设计师无层级链)。
|
||
|
||
export type OwnerEmploymentStatus = '在职' | '已离职' | '未匹配' | '未设置'
|
||
|
||
export const OWNER_EMPLOYMENT_STATUS_VALUES = [
|
||
'在职',
|
||
'已离职',
|
||
'未匹配',
|
||
'未设置',
|
||
] as const satisfies readonly OwnerEmploymentStatus[]
|
||
|
||
export type OwnerEmploymentEntry = {
|
||
/** 人员信息表花名(原样,不清洗) */
|
||
name: string
|
||
/** 在职状态;无员工目录关联的行按在职处理(不标注) */
|
||
sourceStatus: 'EMPLOYED' | 'DIMISSIONED'
|
||
departmentOwner: string
|
||
generalOwner: string
|
||
}
|
||
|
||
/** 与档案侧身份清洗同口径:去零宽字符 + 去首尾空白 + 大写。 */
|
||
export function normalizeOwnerName(value: string) {
|
||
return value.replace(/[\u200B-\u200D\uFEFF]/g, '').trim().toUpperCase()
|
||
}
|
||
|
||
export type OwnerEmploymentIndex = Map<string, OwnerEmploymentEntry>
|
||
|
||
/** 同名花名取第一条(调用方按 operationOwner asc、updatedAt desc 排序传入)。 */
|
||
export function buildOwnerEmploymentIndex(entries: OwnerEmploymentEntry[]): OwnerEmploymentIndex {
|
||
const index: OwnerEmploymentIndex = new Map()
|
||
for (const entry of entries) {
|
||
const key = normalizeOwnerName(entry.name)
|
||
if (key && !index.has(key)) index.set(key, entry)
|
||
}
|
||
return index
|
||
}
|
||
|
||
export type OwnerTakeoverView = {
|
||
status: OwnerEmploymentStatus
|
||
/** 展示值:在职/未匹配/未设置为原样(未设置为空串),离职为 `花名(已离职)→ 接管人` */
|
||
display: string
|
||
/** 接管人花名(仅离职且有接管人时有值) */
|
||
successor: string
|
||
/** 接管人本身也已离职 */
|
||
successorDeparted: boolean
|
||
}
|
||
|
||
export function resolveOwnerTakeover(
|
||
opsHead: string | null | undefined,
|
||
index: OwnerEmploymentIndex,
|
||
): OwnerTakeoverView {
|
||
const raw = (opsHead ?? '').trim()
|
||
if (!raw) return { status: '未设置', display: '', successor: '', successorDeparted: false }
|
||
const entry = index.get(normalizeOwnerName(raw))
|
||
if (!entry) return { status: '未匹配', display: raw, successor: '', successorDeparted: false }
|
||
if (entry.sourceStatus !== 'DIMISSIONED') {
|
||
return { status: '在职', display: raw, successor: '', successorDeparted: false }
|
||
}
|
||
|
||
const ownerKey = normalizeOwnerName(raw)
|
||
const pickSuccessor = (name: string | null | undefined) => {
|
||
const trimmed = (name ?? '').trim()
|
||
if (!trimmed || normalizeOwnerName(trimmed) === ownerKey) return null
|
||
return {
|
||
name: trimmed,
|
||
departed: index.get(normalizeOwnerName(trimmed))?.sourceStatus === 'DIMISSIONED',
|
||
}
|
||
}
|
||
const departmentOwner = pickSuccessor(entry.departmentOwner)
|
||
const generalOwner = pickSuccessor(entry.generalOwner)
|
||
// 接管链:部门现任负责人在职 → 直接接管;否则交给总负责人(总负责人也离职则
|
||
// 带标注展示,不再上抛);两者都缺位时退回展示部门负责人(如有)。
|
||
const successor = (departmentOwner && !departmentOwner.departed ? departmentOwner : null)
|
||
?? generalOwner
|
||
?? departmentOwner
|
||
if (!successor) {
|
||
return { status: '已离职', display: `${raw}(已离职)`, successor: '', successorDeparted: false }
|
||
}
|
||
return {
|
||
status: '已离职',
|
||
display: `${raw}(已离职)→ ${successor.name}${successor.departed ? '(已离职)' : ''}`,
|
||
successor: successor.name,
|
||
successorDeparted: successor.departed,
|
||
}
|
||
}
|
||
|
||
export function resolveDesignerView(
|
||
designer: string | null | undefined,
|
||
index: OwnerEmploymentIndex,
|
||
): { display: string; departed: boolean } {
|
||
const raw = (designer ?? '').trim()
|
||
if (!raw) return { display: '', departed: false }
|
||
const entry = index.get(normalizeOwnerName(raw))
|
||
if (entry?.sourceStatus === 'DIMISSIONED') {
|
||
return { display: `${raw}(已离职)`, departed: true }
|
||
}
|
||
return { display: raw, departed: false }
|
||
}
|