fix: 恢复直属主管组织解析规则
This commit is contained in:
parent
39f99e35cd
commit
1ef0c81429
@ -80,6 +80,7 @@ jest.mock('@/lib/performance-survey-position-config', () => {
|
||||
jest.mock('@/lib/performance-survey-report-sync', () => ({
|
||||
ensurePerformanceSurveyDailyEmployeeSyncStarted: jest.fn(),
|
||||
ensurePerformanceSurveyEmployeeProfileForUser: jest.fn(),
|
||||
getActivePerformanceSurveyEmployeeProfileWhere: jest.fn(() => ({ employmentStatus: { not: 'inactive' } })),
|
||||
getPerformanceSurveyAccountProfileWhere: jest.fn(async () => ({ id: { not: '__hidden__' } })),
|
||||
previewPerformanceSurveyEmployeeSync: jest.fn(),
|
||||
syncPerformanceSurveyEmployees: jest.fn(),
|
||||
@ -1223,6 +1224,10 @@ describe('performance survey service policies', () => {
|
||||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce([creator, target])
|
||||
.mockResolvedValueOnce([creator])
|
||||
;(prisma.user.findMany as jest.Mock).mockResolvedValueOnce([{
|
||||
id: baseUser.id,
|
||||
dingtalkUserId: baseUser.dingtalkUserId,
|
||||
}])
|
||||
;(prisma.user.findFirst as jest.Mock).mockResolvedValue({
|
||||
id: baseUser.id,
|
||||
dingtalkUserId: baseUser.dingtalkUserId,
|
||||
@ -1448,7 +1453,16 @@ describe('performance survey service policies', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test('直属主管模板遇到人员档案缺少主管唯一值时明确拒绝创建', async () => {
|
||||
test.each([
|
||||
['缺少主组织', []],
|
||||
[
|
||||
'存在多个主组织',
|
||||
[
|
||||
{ sourceDeptId: '1085808623' },
|
||||
{ sourceDeptId: '1085808624' },
|
||||
],
|
||||
],
|
||||
])('直属主管模板在人员档案缺少主管唯一值且%s时明确拒绝创建', async (_label, primaryMemberships) => {
|
||||
const target = {
|
||||
...baseEmployee,
|
||||
id: 'employee-target',
|
||||
@ -1470,6 +1484,8 @@ describe('performance survey service policies', () => {
|
||||
questions: [],
|
||||
scoreBands: [],
|
||||
})
|
||||
;(prisma.performanceSurveyEmployeeDepartmentMembership.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce(primaryMemberships)
|
||||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValueOnce([target])
|
||||
|
||||
await expect(createPerformanceSurvey(session, {
|
||||
@ -1478,33 +1494,36 @@ describe('performance survey service policies', () => {
|
||||
assessmentMonth: '2026-07',
|
||||
targetEmployeeIds: [target.id],
|
||||
})).rejects.toEqual(new PerformanceSurveyApiError(
|
||||
'无法确定缺主管人员的直属主管,请先校准人员档案',
|
||||
'无法确定缺主管人员所属的主组织,请先校准人员档案',
|
||||
409,
|
||||
))
|
||||
expect(prisma.$transaction).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('直属主管模板不按同部门 M 级人员猜测主管', async () => {
|
||||
test('直属主管唯一值缺失时按主组织唯一 M 级人员解析主管', async () => {
|
||||
const target = {
|
||||
...baseEmployee,
|
||||
id: 'employee-yijiang',
|
||||
displayName: '忆江',
|
||||
departmentName: '综合支持部',
|
||||
departmentPath: '集团A/品牌中心/综合支持部',
|
||||
dingtalkUserId: 'ding-yijiang',
|
||||
id: 'employee-xinyu',
|
||||
displayName: '新雨',
|
||||
departmentName: '品牌创意A部',
|
||||
departmentPath: '江苏简尚品牌管理有限公司/品牌中心/品牌创意A部',
|
||||
dingtalkUserId: '1766367441840704',
|
||||
levelType: 'P',
|
||||
positionLevel: 'P4',
|
||||
positionLevel: 'P1',
|
||||
managerDingtalkUserId: null,
|
||||
managerJobNumber: null,
|
||||
}
|
||||
const uniqueManager = {
|
||||
...baseEmployee,
|
||||
id: 'employee-wuxie',
|
||||
displayName: '无邪',
|
||||
id: 'employee-boyan',
|
||||
displayName: '伯言',
|
||||
departmentName: target.departmentName,
|
||||
departmentPath: target.departmentPath,
|
||||
dingtalkUserId: 'ding-wuxie',
|
||||
dingtalkUserId: '09061617282237',
|
||||
levelType: 'M',
|
||||
positionLevel: 'M1',
|
||||
positionLevel: 'M2',
|
||||
isLeadership: true,
|
||||
sourcePayload: { materialUserId: 'user-boyan' },
|
||||
}
|
||||
;(ensurePerformanceSurveyEmployeeProfileForUser as jest.Mock).mockResolvedValue(target)
|
||||
;(getPerformanceSurveyTemplate as jest.Mock).mockResolvedValue({
|
||||
@ -1516,14 +1535,19 @@ describe('performance survey service policies', () => {
|
||||
questions: [],
|
||||
scoreBands: [],
|
||||
})
|
||||
;(prisma.performanceSurveyEmployeeDepartmentMembership.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce([{ sourceDeptId: '1085808623' }])
|
||||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce([target])
|
||||
.mockResolvedValueOnce([uniqueManager])
|
||||
.mockResolvedValueOnce([uniqueManager])
|
||||
;(prisma.user.findMany as jest.Mock).mockResolvedValue([{
|
||||
id: 'user-wuxie',
|
||||
;(prisma.user.findMany as jest.Mock).mockResolvedValueOnce([{
|
||||
id: 'user-boyan',
|
||||
dingtalkUserId: uniqueManager.dingtalkUserId,
|
||||
}])
|
||||
;(prisma.user.findFirst as jest.Mock).mockResolvedValue({
|
||||
id: 'user-boyan',
|
||||
dingtalkUserId: uniqueManager.dingtalkUserId,
|
||||
})
|
||||
;(prisma.performanceSurvey.findUnique as jest.Mock).mockResolvedValue(buildSurvey({
|
||||
templateCode: 'work_cooperation',
|
||||
creatorEmployeeId: target.id,
|
||||
@ -1539,6 +1563,197 @@ describe('performance survey service policies', () => {
|
||||
}],
|
||||
}))
|
||||
|
||||
await createPerformanceSurvey(session, {
|
||||
templateCode: 'work_cooperation',
|
||||
positionGroupCode: 'brand_designer',
|
||||
assessmentMonth: '2026-07',
|
||||
targetEmployeeIds: [target.id],
|
||||
openNow: false,
|
||||
})
|
||||
|
||||
expect(prisma.performanceSurveyEmployeeDepartmentMembership.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
employeeProfileId: target.id,
|
||||
isPrimary: true,
|
||||
},
|
||||
select: { sourceDeptId: true },
|
||||
take: 2,
|
||||
})
|
||||
const managerQuery = (prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mock.calls[1][0]
|
||||
expect(managerQuery.where.AND).toContainEqual({
|
||||
departmentMemberships: {
|
||||
some: {
|
||||
sourceDeptId: '1085808623',
|
||||
isPrimary: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(prisma.user.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
archivedAt: null,
|
||||
status: 'active',
|
||||
isActive: true,
|
||||
},
|
||||
{ id: 'user-boyan' },
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
dingtalkUserId: true,
|
||||
},
|
||||
take: 2,
|
||||
})
|
||||
const assignmentRows = transactionClient.performanceSurveyAssignment.createMany.mock.calls[0][0].data
|
||||
expect(assignmentRows).toEqual([
|
||||
expect.objectContaining({
|
||||
evaluatorEmployeeId: uniqueManager.id,
|
||||
evaluatorDingtalkUserId: uniqueManager.dingtalkUserId,
|
||||
evaluatorName: uniqueManager.displayName,
|
||||
reviewerSource: 'direct_manager',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test.each([
|
||||
['没有有效账户', [], '被考核人的直属主管尚无可用账户,请先校准人员档案'],
|
||||
[
|
||||
'账户钉钉身份与档案不一致',
|
||||
[{ id: 'user-manager', dingtalkUserId: 'ding-account-manager' }],
|
||||
'被考核人的直属主管钉钉身份不一致,请先校准人员档案',
|
||||
],
|
||||
])('主管唯一值已配置但%s时不回退组织推断', async (_label, activeAccounts, expectedMessage) => {
|
||||
const target = {
|
||||
...baseEmployee,
|
||||
id: 'employee-target',
|
||||
displayName: '被考核人',
|
||||
dingtalkUserId: 'ding-target',
|
||||
managerDingtalkUserId: 'ding-declared-manager',
|
||||
}
|
||||
const manager = {
|
||||
...baseEmployee,
|
||||
id: 'employee-manager',
|
||||
displayName: '档案直属主管',
|
||||
dingtalkUserId: 'ding-declared-manager',
|
||||
sourcePayload: { materialUserId: 'user-manager' },
|
||||
}
|
||||
;(ensurePerformanceSurveyEmployeeProfileForUser as jest.Mock).mockResolvedValue(target)
|
||||
;(getPerformanceSurveyTemplate as jest.Mock).mockResolvedValue({
|
||||
code: 'work_cooperation',
|
||||
name: '工作配合度满意度调研',
|
||||
shortName: '工作配合度',
|
||||
surveyType: 'person_period',
|
||||
weight: 0.2,
|
||||
questions: [],
|
||||
scoreBands: [],
|
||||
})
|
||||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce([target])
|
||||
.mockResolvedValueOnce([manager])
|
||||
;(prisma.user.findMany as jest.Mock).mockResolvedValueOnce(activeAccounts)
|
||||
|
||||
await expect(createPerformanceSurvey(session, {
|
||||
templateCode: 'work_cooperation',
|
||||
positionGroupCode: 'brand_designer',
|
||||
assessmentMonth: '2026-07',
|
||||
targetEmployeeIds: [target.id],
|
||||
openNow: false,
|
||||
})).rejects.toEqual(new PerformanceSurveyApiError(expectedMessage, 409))
|
||||
expect(prisma.performanceSurveyEmployeeDepartmentMembership.findMany).not.toHaveBeenCalled()
|
||||
expect(prisma.$transaction).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test.each([
|
||||
['没有M级主管', [], '新雨所属组织内没有可用的M级主管,请先校准人员档案'],
|
||||
[
|
||||
'存在多名M级人员',
|
||||
[
|
||||
{ ...baseEmployee, id: 'employee-manager-1', displayName: '主管甲', levelType: 'M', positionLevel: 'M1' },
|
||||
{ ...baseEmployee, id: 'employee-manager-2', displayName: '主管乙', levelType: 'M', positionLevel: 'M2' },
|
||||
],
|
||||
'新雨所属组织内存在多名M级人员,请明确配置直属主管',
|
||||
],
|
||||
])('直属主管唯一值缺失且主组织%s时拒绝创建', async (_label, managers, expectedMessage) => {
|
||||
const target = {
|
||||
...baseEmployee,
|
||||
id: 'employee-xinyu',
|
||||
displayName: '新雨',
|
||||
departmentName: '品牌创意A部',
|
||||
departmentPath: '江苏简尚品牌管理有限公司/品牌中心/品牌创意A部',
|
||||
dingtalkUserId: '1766367441840704',
|
||||
levelType: 'P',
|
||||
positionLevel: 'P1',
|
||||
managerDingtalkUserId: null,
|
||||
managerJobNumber: null,
|
||||
}
|
||||
;(ensurePerformanceSurveyEmployeeProfileForUser as jest.Mock).mockResolvedValue(target)
|
||||
;(getPerformanceSurveyTemplate as jest.Mock).mockResolvedValue({
|
||||
code: 'work_cooperation',
|
||||
name: '工作配合度满意度调研',
|
||||
shortName: '工作配合度',
|
||||
surveyType: 'person_period',
|
||||
weight: 0.2,
|
||||
questions: [],
|
||||
scoreBands: [],
|
||||
})
|
||||
;(prisma.performanceSurveyEmployeeDepartmentMembership.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce([{ sourceDeptId: '1085808623' }])
|
||||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce([target])
|
||||
.mockResolvedValueOnce(managers)
|
||||
|
||||
await expect(createPerformanceSurvey(session, {
|
||||
templateCode: 'work_cooperation',
|
||||
positionGroupCode: 'brand_designer',
|
||||
assessmentMonth: '2026-07',
|
||||
targetEmployeeIds: [target.id],
|
||||
openNow: false,
|
||||
})).rejects.toEqual(new PerformanceSurveyApiError(expectedMessage, 409))
|
||||
expect(prisma.$transaction).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('直属主管唯一值缺失且主组织唯一 M 级人员无可用账户时拒绝创建', async () => {
|
||||
const target = {
|
||||
...baseEmployee,
|
||||
id: 'employee-xinyu',
|
||||
displayName: '新雨',
|
||||
departmentName: '品牌创意A部',
|
||||
departmentPath: '江苏简尚品牌管理有限公司/品牌中心/品牌创意A部',
|
||||
dingtalkUserId: '1766367441840704',
|
||||
levelType: 'P',
|
||||
positionLevel: 'P1',
|
||||
managerDingtalkUserId: null,
|
||||
managerJobNumber: null,
|
||||
}
|
||||
const uniqueManager = {
|
||||
...baseEmployee,
|
||||
id: 'employee-boyan',
|
||||
displayName: '伯言',
|
||||
departmentName: target.departmentName,
|
||||
departmentPath: target.departmentPath,
|
||||
dingtalkUserId: '09061617282237',
|
||||
levelType: 'M',
|
||||
positionLevel: 'M2',
|
||||
isLeadership: true,
|
||||
sourcePayload: { materialUserId: 'user-boyan' },
|
||||
}
|
||||
;(ensurePerformanceSurveyEmployeeProfileForUser as jest.Mock).mockResolvedValue(target)
|
||||
;(getPerformanceSurveyTemplate as jest.Mock).mockResolvedValue({
|
||||
code: 'work_cooperation',
|
||||
name: '工作配合度满意度调研',
|
||||
shortName: '工作配合度',
|
||||
surveyType: 'person_period',
|
||||
weight: 0.2,
|
||||
questions: [],
|
||||
scoreBands: [],
|
||||
})
|
||||
;(prisma.performanceSurveyEmployeeDepartmentMembership.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce([{ sourceDeptId: '1085808623' }])
|
||||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce([target])
|
||||
.mockResolvedValueOnce([uniqueManager])
|
||||
|
||||
await expect(createPerformanceSurvey(session, {
|
||||
templateCode: 'work_cooperation',
|
||||
positionGroupCode: 'brand_designer',
|
||||
@ -1546,13 +1761,13 @@ describe('performance survey service policies', () => {
|
||||
targetEmployeeIds: [target.id],
|
||||
openNow: false,
|
||||
})).rejects.toEqual(new PerformanceSurveyApiError(
|
||||
'无法确定忆江的直属主管,请先校准人员档案',
|
||||
'新雨的直属主管尚无可用账户,请先校准人员档案',
|
||||
409,
|
||||
))
|
||||
expect(prisma.$transaction).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('人员姓名不再触发固定评分人规则,缺主管仍按档案错误处理', async () => {
|
||||
test('人员姓名不触发固定评分人规则,缺主管时仍按组织规则处理', async () => {
|
||||
const yuanzhe = {
|
||||
...baseEmployee,
|
||||
id: 'employee-yuanzhe',
|
||||
@ -1574,7 +1789,11 @@ describe('performance survey service policies', () => {
|
||||
questions: [],
|
||||
scoreBands: [],
|
||||
})
|
||||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValueOnce([yuanzhe])
|
||||
;(prisma.performanceSurveyEmployeeDepartmentMembership.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce([{ sourceDeptId: '1085808623' }])
|
||||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock)
|
||||
.mockResolvedValueOnce([yuanzhe])
|
||||
.mockResolvedValueOnce([])
|
||||
|
||||
await expect(createPerformanceSurvey(session, {
|
||||
templateCode: 'work_cooperation',
|
||||
@ -1582,7 +1801,7 @@ describe('performance survey service policies', () => {
|
||||
assessmentMonth: '2026-07',
|
||||
targetEmployeeIds: [yuanzhe.id],
|
||||
})).rejects.toEqual(new PerformanceSurveyApiError(
|
||||
'无法确定元泽的直属主管,请先校准人员档案',
|
||||
'元泽所属组织内没有可用的M级主管,请先校准人员档案',
|
||||
409,
|
||||
))
|
||||
expect(prisma.$transaction).not.toHaveBeenCalled()
|
||||
@ -1632,10 +1851,10 @@ describe('performance survey service policies', () => {
|
||||
.mockResolvedValueOnce([manager])
|
||||
;(prisma.performanceSurveyEmployeeProfile.findFirst as jest.Mock).mockReset()
|
||||
;(prisma.performanceSurveyEmployeeProfile.findFirst as jest.Mock).mockResolvedValue(yuanzhe)
|
||||
;(prisma.user.findMany as jest.Mock).mockResolvedValue([
|
||||
{ id: 'user-1', dingtalkUserId: 'ding-yuanzhe' },
|
||||
{ id: 'user-manager', dingtalkUserId: 'ding-manager' },
|
||||
])
|
||||
;(prisma.user.findMany as jest.Mock).mockResolvedValue([{
|
||||
id: 'user-manager',
|
||||
dingtalkUserId: 'ding-manager',
|
||||
}])
|
||||
;(prisma.performanceSurvey.findUnique as jest.Mock).mockResolvedValue(buildSurvey({
|
||||
templateCode: 'visual_brand_tone',
|
||||
targets: [{
|
||||
@ -1725,10 +1944,10 @@ describe('performance survey service policies', () => {
|
||||
.mockResolvedValueOnce([target])
|
||||
.mockResolvedValueOnce([manager])
|
||||
;(prisma.performanceSurveyEmployeeProfile.findFirst as jest.Mock).mockResolvedValue(yuanzhe)
|
||||
;(prisma.user.findMany as jest.Mock).mockResolvedValue([
|
||||
{ id: 'user-target', dingtalkUserId },
|
||||
{ id: 'user-yuanzhe', dingtalkUserId: '27102756692071120919' },
|
||||
])
|
||||
;(prisma.user.findMany as jest.Mock).mockResolvedValue([{
|
||||
id: 'user-manager',
|
||||
dingtalkUserId: 'ding-declared-manager',
|
||||
}])
|
||||
;(prisma.performanceSurvey.findUnique as jest.Mock).mockResolvedValue(buildSurvey({
|
||||
templateCode: 'visual_brand_tone',
|
||||
targets: [{
|
||||
@ -1812,11 +2031,10 @@ describe('performance survey service policies', () => {
|
||||
.mockResolvedValueOnce([target])
|
||||
.mockResolvedValueOnce([guanshi])
|
||||
;(prisma.performanceSurveyEmployeeProfile.findFirst as jest.Mock).mockResolvedValue(yuanzhe)
|
||||
;(prisma.user.findMany as jest.Mock).mockResolvedValue([
|
||||
{ id: 'user-target', dingtalkUserId: 'ding-photographer' },
|
||||
{ id: 'user-yuanzhe', dingtalkUserId: '27102756692071120919' },
|
||||
{ id: 'user-guanshi', dingtalkUserId: '17642061233583361' },
|
||||
])
|
||||
;(prisma.user.findMany as jest.Mock).mockResolvedValue([{
|
||||
id: 'user-guanshi',
|
||||
dingtalkUserId: '17642061233583361',
|
||||
}])
|
||||
;(prisma.performanceSurvey.findUnique as jest.Mock).mockResolvedValue(buildSurvey({
|
||||
templateCode: 'visual_brand_tone',
|
||||
targets: [{
|
||||
|
||||
@ -64,6 +64,7 @@ import {
|
||||
import {
|
||||
ensurePerformanceSurveyDailyEmployeeSyncStarted,
|
||||
ensurePerformanceSurveyEmployeeProfileForUser,
|
||||
getActivePerformanceSurveyEmployeeProfileWhere,
|
||||
getPerformanceSurveyAccountProfileWhere,
|
||||
previewPerformanceSurveyEmployeeSync,
|
||||
syncPerformanceSurveyEmployees,
|
||||
@ -4835,31 +4836,143 @@ async function resolveEmployeeEvaluator(employee: any) {
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveAvailableDirectManagerProfile(targetEmployee: any, manager: any) {
|
||||
const sourcePayload = manager?.sourcePayload && typeof manager.sourcePayload === 'object'
|
||||
? manager.sourcePayload as Record<string, unknown>
|
||||
: {}
|
||||
const linkedUserId = clean(sourcePayload.materialUserId) || clean(sourcePayload.userId)
|
||||
const managerDingtalkIdentity = clean(manager?.dingtalkUserId)
|
||||
const managerEmail = clean(manager?.dingEmail)
|
||||
const accountIdentityWhere = linkedUserId
|
||||
? { id: linkedUserId }
|
||||
: managerDingtalkIdentity
|
||||
? { dingtalkUserId: managerDingtalkIdentity }
|
||||
: managerEmail
|
||||
? { email: { equals: managerEmail, mode: 'insensitive' as const } }
|
||||
: null
|
||||
if (!accountIdentityWhere) {
|
||||
throw new PerformanceSurveyApiError(`${getEmployeeDisplayName(targetEmployee)}的直属主管缺少可用登录身份,请先校准人员档案`, 409)
|
||||
}
|
||||
|
||||
const activeAccounts = await prisma.user.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
archivedAt: null,
|
||||
status: 'active',
|
||||
isActive: true,
|
||||
},
|
||||
accountIdentityWhere,
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
dingtalkUserId: true,
|
||||
},
|
||||
take: 2,
|
||||
})
|
||||
if (activeAccounts.length !== 1) {
|
||||
throw new PerformanceSurveyApiError(`${getEmployeeDisplayName(targetEmployee)}的直属主管尚无可用账户,请先校准人员档案`, 409)
|
||||
}
|
||||
|
||||
const accountDingtalkUserId = clean(activeAccounts[0].dingtalkUserId)
|
||||
if (
|
||||
managerDingtalkIdentity
|
||||
&& managerDingtalkIdentity !== accountDingtalkUserId
|
||||
) {
|
||||
throw new PerformanceSurveyApiError(`${getEmployeeDisplayName(targetEmployee)}的直属主管钉钉身份不一致,请先校准人员档案`, 409)
|
||||
}
|
||||
const resolvedDingtalkUserId = managerDingtalkIdentity || accountDingtalkUserId
|
||||
if (!resolvedDingtalkUserId) {
|
||||
throw new PerformanceSurveyApiError(`${getEmployeeDisplayName(targetEmployee)}的直属主管缺少钉钉唯一值,请先校准人员档案`, 409)
|
||||
}
|
||||
return {
|
||||
...manager,
|
||||
dingtalkUserId: resolvedDingtalkUserId,
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveDirectManagerForWorkflow(targetEmployee: any) {
|
||||
const accountWhere = await getPerformanceSurveyAccountProfileWhere()
|
||||
const managerDingtalkUserId = clean(targetEmployee?.managerDingtalkUserId)
|
||||
const managerJobNumber = clean(targetEmployee?.managerJobNumber)
|
||||
if (!managerDingtalkUserId && !managerJobNumber) {
|
||||
throw new PerformanceSurveyApiError(`无法确定${getEmployeeDisplayName(targetEmployee)}的直属主管,请先校准人员档案`, 409)
|
||||
|
||||
// 上游已提供主管唯一值时只做精确匹配;值失效或冲突不能用组织推断掩盖。
|
||||
if (managerDingtalkUserId || managerJobNumber) {
|
||||
const managers = await prisma.performanceSurveyEmployeeProfile.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
getActivePerformanceSurveyEmployeeProfileWhere(),
|
||||
{
|
||||
OR: [
|
||||
...(managerDingtalkUserId ? [{ dingtalkUserId: managerDingtalkUserId }] : []),
|
||||
...(managerJobNumber ? [{ jobNumber: managerJobNumber }] : []),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
take: 2,
|
||||
})
|
||||
if (managers.length !== 1) {
|
||||
throw new PerformanceSurveyApiError(`无法唯一匹配${getEmployeeDisplayName(targetEmployee)}的直属主管`, 409)
|
||||
}
|
||||
return resolveAvailableDirectManagerProfile(targetEmployee, managers[0])
|
||||
}
|
||||
|
||||
// 只有主管唯一值完全缺失时,才按双方的主组织唯一值推断唯一在职 M 级人员。
|
||||
const targetEmployeeId = clean(targetEmployee?.id)
|
||||
if (!targetEmployeeId) {
|
||||
throw new PerformanceSurveyApiError(`无法确定${getEmployeeDisplayName(targetEmployee)}所属的主组织,请先校准人员档案`, 409)
|
||||
}
|
||||
|
||||
const primaryMemberships = await prisma.performanceSurveyEmployeeDepartmentMembership.findMany({
|
||||
where: {
|
||||
employeeProfileId: targetEmployeeId,
|
||||
isPrimary: true,
|
||||
},
|
||||
select: { sourceDeptId: true },
|
||||
take: 2,
|
||||
})
|
||||
const primarySourceDeptIds = Array.from(new Set(
|
||||
primaryMemberships
|
||||
.map((membership) => clean(membership.sourceDeptId))
|
||||
.filter((sourceDeptId): sourceDeptId is string => Boolean(sourceDeptId)),
|
||||
))
|
||||
if (primarySourceDeptIds.length !== 1) {
|
||||
throw new PerformanceSurveyApiError(`无法确定${getEmployeeDisplayName(targetEmployee)}所属的主组织,请先校准人员档案`, 409)
|
||||
}
|
||||
|
||||
const managers = await prisma.performanceSurveyEmployeeProfile.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
accountWhere,
|
||||
getActivePerformanceSurveyEmployeeProfileWhere(),
|
||||
{ id: { not: targetEmployeeId } },
|
||||
{
|
||||
OR: [
|
||||
...(managerDingtalkUserId ? [{ dingtalkUserId: managerDingtalkUserId }] : []),
|
||||
...(managerJobNumber ? [{ jobNumber: managerJobNumber }] : []),
|
||||
{ isLeadership: true },
|
||||
{ levelType: { equals: 'M', mode: 'insensitive' } },
|
||||
{ positionLevel: { startsWith: 'M', mode: 'insensitive' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
departmentMemberships: {
|
||||
some: {
|
||||
sourceDeptId: primarySourceDeptIds[0],
|
||||
isPrimary: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
take: 2,
|
||||
})
|
||||
if (managers.length !== 1) {
|
||||
throw new PerformanceSurveyApiError(`无法唯一匹配${getEmployeeDisplayName(targetEmployee)}的直属主管`, 409)
|
||||
if (managers.length === 0) {
|
||||
throw new PerformanceSurveyApiError(`${getEmployeeDisplayName(targetEmployee)}所属组织内没有可用的M级主管,请先校准人员档案`, 409)
|
||||
}
|
||||
return managers[0]
|
||||
if (managers.length > 1) {
|
||||
throw new PerformanceSurveyApiError(`${getEmployeeDisplayName(targetEmployee)}所属组织内存在多名M级人员,请明确配置直属主管`, 409)
|
||||
}
|
||||
|
||||
return resolveAvailableDirectManagerProfile(targetEmployee, managers[0])
|
||||
}
|
||||
|
||||
async function resolveConfiguredWorkflowEvaluators(input: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user