fix: scope performance survey roster sync
This commit is contained in:
parent
57f46fca6f
commit
7b4d70360d
@ -38,6 +38,7 @@ type DepartmentOption = {
|
||||
value?: string | null
|
||||
departmentName?: string | null
|
||||
departmentPath?: string | null
|
||||
fullPath?: string | null
|
||||
}
|
||||
|
||||
type Template = {
|
||||
@ -638,6 +639,7 @@ function DepartmentPicker({
|
||||
department.parentName,
|
||||
department.departmentName,
|
||||
department.departmentPath,
|
||||
department.fullPath,
|
||||
].filter(Boolean).join(' ').toLowerCase()
|
||||
if (!haystack.includes(normalizedKeyword)) return
|
||||
|
||||
@ -761,9 +763,11 @@ function DepartmentPicker({
|
||||
onClick={() => department.value && toggleValue(department.value, false)}
|
||||
className="rounded-md bg-gray-100 px-2.5 py-1 text-sm text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200"
|
||||
>
|
||||
{department.parentName && department.parentName !== '全部部门'
|
||||
? `${department.parentName} / ${department.name}`
|
||||
: department.name} ×
|
||||
{department.fullPath || department.departmentPath || (
|
||||
department.parentName && department.parentName !== '全部部门'
|
||||
? `${department.parentName} / ${department.name}`
|
||||
: department.name
|
||||
)} ×
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -6,6 +6,7 @@ type ReportEmployeeRow = {
|
||||
dingtalkUserId?: string | null
|
||||
dingEmail?: string | null
|
||||
jobNumber?: string | null
|
||||
employeeDeptIdList?: string | null
|
||||
contactName?: string | null
|
||||
contactTitle?: string | null
|
||||
workPlace?: string | null
|
||||
@ -18,6 +19,8 @@ type ReportEmployeeRow = {
|
||||
positionLevel?: string | null
|
||||
departmentName?: string | null
|
||||
departmentPath?: string | null
|
||||
registerDeptIds?: string | null
|
||||
registerMainDeptId?: string | null
|
||||
employmentStatus?: string | null
|
||||
managerDingtalkUserId?: string | null
|
||||
managerJobNumber?: string | null
|
||||
@ -25,6 +28,7 @@ type ReportEmployeeRow = {
|
||||
flowerBookName?: string | null
|
||||
flowerBookFlowerName?: string | null
|
||||
flowerBookDept?: string | null
|
||||
flowerBookDeptId?: string | null
|
||||
flowerBookWorkPlace?: string | null
|
||||
flowerBookJob?: string | null
|
||||
flowerBookJobLevel?: string | null
|
||||
@ -41,6 +45,8 @@ type ReportIdentity = {
|
||||
realName?: string | null
|
||||
displayName?: string | null
|
||||
flowerName?: string | null
|
||||
userId?: string | null
|
||||
department?: string | null
|
||||
}
|
||||
|
||||
type TableColumns = Map<string, string>
|
||||
@ -69,6 +75,7 @@ const FIELD_CANDIDATES = {
|
||||
workPlace: ['work_place', 'workPlace', 'workplace', 'office_location', '办公地点'],
|
||||
extension: ['extension', 'ext', 'extattr', 'ext_attr'],
|
||||
active: ['active', 'is_active', 'enabled'],
|
||||
employeeDeptIdList: ['dept_id_list', 'deptIdList', 'department_id_list', '部门id列表'],
|
||||
registerName: ['sys00-name', 'name', '姓名'],
|
||||
flowerName: ['sys00-flowerName', 'flower_name', 'flowerName', '花名'],
|
||||
registerRealName: ['sys02-realName', 'real_name', 'realName', '真实姓名'],
|
||||
@ -77,6 +84,8 @@ const FIELD_CANDIDATES = {
|
||||
positionLevel: ['sys01-positionLevel', 'position_level', 'positionLevel', '岗位职级', '职级'],
|
||||
departmentName: ['sys00-dept', 'department', 'department_name', 'dept_name', '部门'],
|
||||
departmentPath: ['sys00-mainDepts', 'department_path', 'main_departments', '部门路径'],
|
||||
registerDeptIds: ['sys00-deptIds', 'dept_ids', 'department_ids', '部门ids'],
|
||||
registerMainDeptId: ['sys00-mainDeptId', 'main_dept_id', 'mainDepartmentId', '主部门id'],
|
||||
employmentStatus: ['sys01-employeeStatus', 'employee_status', 'employment_status', '在职状态'],
|
||||
managerDingtalkUserId: ['manager_userid', 'managerUserid', 'leader_userid', '主管userid'],
|
||||
managerJobNumber: ['manager_job_number', 'managerJobNumber', 'leader_job_number', '主管工号'],
|
||||
@ -85,6 +94,7 @@ const FIELD_CANDIDATES = {
|
||||
flowerBookName: ['name', '姓名'],
|
||||
flowerBookFlowerName: ['flowerName', 'flower_name', '花名'],
|
||||
flowerBookDept: ['dept', 'department', 'department_name', '部门'],
|
||||
flowerBookDeptId: ['deptId', 'dept_id', 'department_id', '部门id'],
|
||||
flowerBookWorkPlace: ['workPlace', 'work_place', '办公地点'],
|
||||
flowerBookJob: ['job', 'position', '岗位'],
|
||||
flowerBookJobLevel: ['jobLevel', 'job_level', '岗位职级', '职级'],
|
||||
@ -155,21 +165,55 @@ function parseExtension(value?: string | null) {
|
||||
}
|
||||
|
||||
function parseDepartmentValue(value?: string | null) {
|
||||
const parts = parseDepartmentParts(value)
|
||||
if (parts.length > 0) return parts[parts.length - 1] || parts[0] || null
|
||||
|
||||
const normalized = clean(value)
|
||||
if (!normalized) return null
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
function parseDepartmentParts(value?: string | null) {
|
||||
const normalized = clean(value)
|
||||
if (!normalized) return []
|
||||
|
||||
if (normalized.startsWith('[')) {
|
||||
try {
|
||||
const parsed = JSON.parse(normalized)
|
||||
if (Array.isArray(parsed)) {
|
||||
return clean(parsed[parsed.length - 1]) || clean(parsed[0])
|
||||
return parsed.map((item) => clean(item)).filter((item): item is string => Boolean(item))
|
||||
}
|
||||
} catch {
|
||||
return normalized
|
||||
// 继续按普通字符串拆分,兼容报表库里偶发的非标准 JSON 文本。
|
||||
}
|
||||
}
|
||||
|
||||
return normalized
|
||||
.split(/[/>||,,;;]/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function getDepartmentParts(row: ReportEmployeeRow) {
|
||||
const flowerBookParts = parseDepartmentParts(row.flowerBookDept)
|
||||
if (flowerBookParts.length > 0) return flowerBookParts
|
||||
|
||||
const pathParts = parseDepartmentParts(row.departmentPath)
|
||||
const nameParts = parseDepartmentParts(row.departmentName)
|
||||
if (pathParts.length === 0) return nameParts
|
||||
if (nameParts.length === 0) return pathParts
|
||||
|
||||
const missingNameParts = nameParts.filter((part) => !pathParts.includes(part))
|
||||
return missingNameParts.length > 0 ? [...missingNameParts, ...pathParts] : pathParts
|
||||
}
|
||||
|
||||
function joinDepartmentParts(parts: string[]) {
|
||||
return parts.length > 0 ? parts.join('/') : null
|
||||
}
|
||||
|
||||
function getDepartmentLeaf(parts: string[]) {
|
||||
return parts.length > 0 ? parts[parts.length - 1] || parts[0] || null : null
|
||||
}
|
||||
|
||||
function getLevelType(positionLevel?: string | null) {
|
||||
@ -266,6 +310,7 @@ function buildSelectSql(
|
||||
workPlace: pickColumn(employeeColumns, FIELD_CANDIDATES.workPlace),
|
||||
extension: pickColumn(employeeColumns, FIELD_CANDIDATES.extension),
|
||||
active: pickColumn(employeeColumns, FIELD_CANDIDATES.active),
|
||||
deptIdList: pickColumn(employeeColumns, FIELD_CANDIDATES.employeeDeptIdList),
|
||||
managerDingtalkUserId: pickColumn(employeeColumns, FIELD_CANDIDATES.managerDingtalkUserId),
|
||||
managerJobNumber: pickColumn(employeeColumns, FIELD_CANDIDATES.managerJobNumber),
|
||||
managerName: pickColumn(employeeColumns, FIELD_CANDIDATES.managerName),
|
||||
@ -281,6 +326,8 @@ function buildSelectSql(
|
||||
positionLevel: pickColumn(registerColumns, FIELD_CANDIDATES.positionLevel),
|
||||
departmentName: pickColumn(registerColumns, FIELD_CANDIDATES.departmentName),
|
||||
departmentPath: pickColumn(registerColumns, FIELD_CANDIDATES.departmentPath),
|
||||
deptIds: pickColumn(registerColumns, FIELD_CANDIDATES.registerDeptIds),
|
||||
mainDeptId: pickColumn(registerColumns, FIELD_CANDIDATES.registerMainDeptId),
|
||||
employmentStatus: pickColumn(registerColumns, FIELD_CANDIDATES.employmentStatus),
|
||||
managerDingtalkUserId: pickColumn(registerColumns, FIELD_CANDIDATES.managerDingtalkUserId),
|
||||
managerJobNumber: pickColumn(registerColumns, FIELD_CANDIDATES.managerJobNumber),
|
||||
@ -291,6 +338,7 @@ function buildSelectSql(
|
||||
name: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookName),
|
||||
flowerName: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookFlowerName),
|
||||
dept: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookDept),
|
||||
deptId: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookDeptId),
|
||||
workPlace: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookWorkPlace),
|
||||
job: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookJob),
|
||||
jobLevel: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookJobLevel),
|
||||
@ -321,6 +369,7 @@ function buildSelectSql(
|
||||
${columnExpr('e', e.title)} AS "contactTitle",
|
||||
${columnExpr('e', e.workPlace)} AS "workPlace",
|
||||
${columnExpr('e', e.extension)} AS "extension",
|
||||
${columnExpr('e', e.deptIdList)} AS "employeeDeptIdList",
|
||||
${columnExpr('r', r.registerName)} AS "registerName",
|
||||
${columnExpr('r', r.flowerName)} AS "flowerName",
|
||||
${columnExpr('r', r.realName)} AS "realName",
|
||||
@ -329,6 +378,8 @@ function buildSelectSql(
|
||||
${columnExpr('r', r.positionLevel)} AS "positionLevel",
|
||||
${columnExpr('r', r.departmentName)} AS "departmentName",
|
||||
${columnExpr('r', r.departmentPath)} AS "departmentPath",
|
||||
${columnExpr('r', r.deptIds)} AS "registerDeptIds",
|
||||
${columnExpr('r', r.mainDeptId)} AS "registerMainDeptId",
|
||||
${columnExpr('r', r.employmentStatus)} AS "employmentStatus",
|
||||
COALESCE(${columnExpr('r', r.managerDingtalkUserId)}, ${columnExpr('e', e.managerDingtalkUserId)}) AS "managerDingtalkUserId",
|
||||
COALESCE(${columnExpr('r', r.managerJobNumber)}, ${columnExpr('e', e.managerJobNumber)}) AS "managerJobNumber",
|
||||
@ -336,6 +387,7 @@ function buildSelectSql(
|
||||
${columnExpr('f', f.name)} AS "flowerBookName",
|
||||
${columnExpr('f', f.flowerName)} AS "flowerBookFlowerName",
|
||||
${columnExpr('f', f.dept)} AS "flowerBookDept",
|
||||
${columnExpr('f', f.deptId)} AS "flowerBookDeptId",
|
||||
${columnExpr('f', f.workPlace)} AS "flowerBookWorkPlace",
|
||||
${columnExpr('f', f.job)} AS "flowerBookJob",
|
||||
${columnExpr('f', f.jobLevel)} AS "flowerBookJobLevel",
|
||||
@ -362,6 +414,7 @@ function buildEmployeeListSql(employeeColumns: TableColumns) {
|
||||
workPlace: pickColumn(employeeColumns, FIELD_CANDIDATES.workPlace),
|
||||
extension: pickColumn(employeeColumns, FIELD_CANDIDATES.extension),
|
||||
active: pickColumn(employeeColumns, FIELD_CANDIDATES.active),
|
||||
deptIdList: pickColumn(employeeColumns, FIELD_CANDIDATES.employeeDeptIdList),
|
||||
managerDingtalkUserId: pickColumn(employeeColumns, FIELD_CANDIDATES.managerDingtalkUserId),
|
||||
managerJobNumber: pickColumn(employeeColumns, FIELD_CANDIDATES.managerJobNumber),
|
||||
managerName: pickColumn(employeeColumns, FIELD_CANDIDATES.managerName),
|
||||
@ -377,6 +430,7 @@ function buildEmployeeListSql(employeeColumns: TableColumns) {
|
||||
${columnExpr('e', e.title)} AS "contactTitle",
|
||||
${columnExpr('e', e.workPlace)} AS "workPlace",
|
||||
${columnExpr('e', e.extension)} AS "extension",
|
||||
${columnExpr('e', e.deptIdList)} AS "employeeDeptIdList",
|
||||
${columnExpr('e', e.managerDingtalkUserId)} AS "managerDingtalkUserId",
|
||||
${columnExpr('e', e.managerJobNumber)} AS "managerJobNumber",
|
||||
${columnExpr('e', e.managerName)} AS "managerName"
|
||||
@ -398,6 +452,8 @@ function buildRegisterListSql(registerColumns: TableColumns) {
|
||||
positionLevel: pickColumn(registerColumns, FIELD_CANDIDATES.positionLevel),
|
||||
departmentName: pickColumn(registerColumns, FIELD_CANDIDATES.departmentName),
|
||||
departmentPath: pickColumn(registerColumns, FIELD_CANDIDATES.departmentPath),
|
||||
deptIds: pickColumn(registerColumns, FIELD_CANDIDATES.registerDeptIds),
|
||||
mainDeptId: pickColumn(registerColumns, FIELD_CANDIDATES.registerMainDeptId),
|
||||
employmentStatus: pickColumn(registerColumns, FIELD_CANDIDATES.employmentStatus),
|
||||
managerDingtalkUserId: pickColumn(registerColumns, FIELD_CANDIDATES.managerDingtalkUserId),
|
||||
managerJobNumber: pickColumn(registerColumns, FIELD_CANDIDATES.managerJobNumber),
|
||||
@ -416,6 +472,8 @@ function buildRegisterListSql(registerColumns: TableColumns) {
|
||||
${columnExpr('r', r.positionLevel)} AS "positionLevel",
|
||||
${columnExpr('r', r.departmentName)} AS "departmentName",
|
||||
${columnExpr('r', r.departmentPath)} AS "departmentPath",
|
||||
${columnExpr('r', r.deptIds)} AS "registerDeptIds",
|
||||
${columnExpr('r', r.mainDeptId)} AS "registerMainDeptId",
|
||||
${columnExpr('r', r.employmentStatus)} AS "employmentStatus",
|
||||
${columnExpr('r', r.managerDingtalkUserId)} AS "managerDingtalkUserId",
|
||||
${columnExpr('r', r.managerJobNumber)} AS "managerJobNumber",
|
||||
@ -431,6 +489,7 @@ function buildFlowerListSql(flowerColumns: TableColumns) {
|
||||
name: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookName),
|
||||
flowerName: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookFlowerName),
|
||||
dept: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookDept),
|
||||
deptId: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookDeptId),
|
||||
workPlace: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookWorkPlace),
|
||||
job: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookJob),
|
||||
jobLevel: pickColumn(flowerColumns, FIELD_CANDIDATES.flowerBookJobLevel),
|
||||
@ -446,6 +505,7 @@ function buildFlowerListSql(flowerColumns: TableColumns) {
|
||||
${columnExpr('f', f.name)} AS "flowerBookName",
|
||||
${columnExpr('f', f.flowerName)} AS "flowerBookFlowerName",
|
||||
${columnExpr('f', f.dept)} AS "flowerBookDept",
|
||||
${columnExpr('f', f.deptId)} AS "flowerBookDeptId",
|
||||
${columnExpr('f', f.workPlace)} AS "flowerBookWorkPlace",
|
||||
${columnExpr('f', f.job)} AS "flowerBookJob",
|
||||
${columnExpr('f', f.jobLevel)} AS "flowerBookJobLevel",
|
||||
@ -479,6 +539,70 @@ function indexRows(rows: ReportEmployeeRow[], keys: Array<keyof ReportEmployeeRo
|
||||
return index
|
||||
}
|
||||
|
||||
function getSyncableUserWhere() {
|
||||
return {
|
||||
archivedAt: null,
|
||||
NOT: [
|
||||
{ status: 'archived' },
|
||||
{ status: 'suspended' },
|
||||
],
|
||||
} as any
|
||||
}
|
||||
|
||||
async function listMaterialAccountSyncIdentities(limit?: number) {
|
||||
const users = await prisma.user.findMany({
|
||||
where: getSyncableUserWhere(),
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
username: true,
|
||||
realName: true,
|
||||
dingtalkUserId: true,
|
||||
department: true,
|
||||
},
|
||||
orderBy: [{ updatedAt: 'desc' }, { createdAt: 'desc' }],
|
||||
...(limit ? { take: limit } : {}),
|
||||
})
|
||||
|
||||
return users.map((user) => ({
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
username: user.username,
|
||||
realName: user.realName,
|
||||
displayName: clean(user.realName) || clean(user.username),
|
||||
flowerName: clean(user.realName) || clean(user.username),
|
||||
dingtalkUserId: user.dingtalkUserId,
|
||||
department: user.department,
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getPerformanceSurveyAccountProfileWhere() {
|
||||
const identities = await listMaterialAccountSyncIdentities()
|
||||
const dingtalkUserIds = Array.from(new Set(identities.map((item) => clean(item.dingtalkUserId)).filter(Boolean))) as string[]
|
||||
const emails = Array.from(new Set(identities.map((item) => clean(item.email)).filter(Boolean))) as string[]
|
||||
const names = Array.from(new Set(
|
||||
identities
|
||||
.flatMap((item) => [item.flowerName, item.displayName, item.realName, item.username])
|
||||
.map((item) => clean(item))
|
||||
.filter(Boolean),
|
||||
)) as string[]
|
||||
|
||||
const or: any[] = []
|
||||
if (dingtalkUserIds.length > 0) {
|
||||
or.push({ dingtalkUserId: { in: dingtalkUserIds } })
|
||||
}
|
||||
if (emails.length > 0) {
|
||||
or.push({ dingEmail: { in: emails } })
|
||||
}
|
||||
if (names.length > 0) {
|
||||
or.push({ displayName: { in: names } })
|
||||
or.push({ flowerName: { in: names } })
|
||||
or.push({ realName: { in: names } })
|
||||
}
|
||||
|
||||
return or.length > 0 ? { OR: or } : { id: { in: [] } }
|
||||
}
|
||||
|
||||
function getIndexed(index: Map<string, ReportEmployeeRow>, ...keys: unknown[]) {
|
||||
for (const key of keys) {
|
||||
const normalized = normalizedKey(key)
|
||||
@ -630,7 +754,9 @@ function normalizeEmployeeRow(row: ReportEmployeeRow) {
|
||||
const displayName = flowerName || realName || jobNumber || dingtalkUserId || dingEmail || '未命名员工'
|
||||
const positionLevel = clean(row.flowerBookJobLevel) || clean(row.positionLevel) || clean(extension['岗位职级'])
|
||||
const levelType = getLevelType(positionLevel)
|
||||
const departmentName = parseDepartmentValue(row.flowerBookDept) || clean(row.departmentName)
|
||||
const departmentParts = getDepartmentParts(row)
|
||||
const departmentName = getDepartmentLeaf(departmentParts) || parseDepartmentValue(row.flowerBookDept) || clean(row.departmentName)
|
||||
const departmentPath = joinDepartmentParts(departmentParts) || clean(row.departmentPath)
|
||||
const positionTitle = clean(row.flowerBookJob) || clean(row.positionTitle) || clean(row.contactTitle)
|
||||
const managerName = clean(row.flowerBookManager) || clean(row.flowerBookDirector) || clean(row.managerName) || clean(extension['主管'])
|
||||
const employmentStatus = clean(row.flowerBookEmployeeStatus) || clean(row.employmentStatus)
|
||||
@ -644,7 +770,7 @@ function normalizeEmployeeRow(row: ReportEmployeeRow) {
|
||||
realName,
|
||||
displayName,
|
||||
departmentName,
|
||||
departmentPath: clean(row.departmentPath),
|
||||
departmentPath,
|
||||
positionTitle,
|
||||
positionLevel,
|
||||
levelType,
|
||||
@ -662,6 +788,11 @@ function normalizeEmployeeRow(row: ReportEmployeeRow) {
|
||||
jobNumber,
|
||||
displayName,
|
||||
departmentName,
|
||||
departmentPath,
|
||||
employeeDeptIdList: clean(row.employeeDeptIdList),
|
||||
registerDeptIds: clean(row.registerDeptIds),
|
||||
registerMainDeptId: clean(row.registerMainDeptId),
|
||||
flowerBookDeptId: clean(row.flowerBookDeptId),
|
||||
positionLevel,
|
||||
levelType,
|
||||
flowerBookRole: clean(row.flowerBookRole),
|
||||
@ -702,19 +833,20 @@ async function upsertEmployeeProfile(data: ReturnType<typeof normalizeEmployeeRo
|
||||
|
||||
function getParentDepartmentName(departmentName?: string | null, departmentPath?: string | null) {
|
||||
const current = clean(departmentName)
|
||||
const parts = String(departmentPath || '')
|
||||
.split(/[/>||,,;;]/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
const parts = parseDepartmentParts(departmentPath)
|
||||
if (!current || parts.length < 2) return null
|
||||
const currentIndex = parts.lastIndexOf(current)
|
||||
return currentIndex > 0 ? parts[currentIndex - 1] : parts[parts.length - 2] || null
|
||||
}
|
||||
|
||||
async function upsertDepartments() {
|
||||
const accountWhere = await getPerformanceSurveyAccountProfileWhere()
|
||||
const employees = await prisma.performanceSurveyEmployeeProfile.findMany({
|
||||
where: {
|
||||
departmentName: { not: null },
|
||||
AND: [
|
||||
accountWhere,
|
||||
{ departmentName: { not: null } },
|
||||
],
|
||||
},
|
||||
select: {
|
||||
departmentName: true,
|
||||
@ -770,41 +902,98 @@ async function getReportPool() {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchNormalizedEmployeeByIdentityWithContext(
|
||||
pool: any,
|
||||
sql: typeof import('mssql'),
|
||||
schema: Awaited<ReturnType<typeof getReportSchema>>,
|
||||
identity: ReportIdentity,
|
||||
) {
|
||||
const where = buildIdentityWhere(
|
||||
schema.employeeColumns,
|
||||
schema.registerColumns,
|
||||
schema.flowerColumns,
|
||||
identity,
|
||||
)
|
||||
if (where.whereSql === '1 = 0') return null
|
||||
|
||||
const request = pool.request().input('limit', sql.Int, 1)
|
||||
if (clean(identity.dingtalkUserId)) request.input('dingtalkUserId', sql.NVarChar(200), clean(identity.dingtalkUserId))
|
||||
if (clean(identity.email)) request.input('email', sql.NVarChar(320), clean(identity.email))
|
||||
if (where.nameParam) request.input('name', sql.NVarChar(200), where.nameParam)
|
||||
|
||||
const result = await request.query(
|
||||
buildSelectSql(
|
||||
schema.employeeColumns,
|
||||
schema.registerColumns,
|
||||
schema.flowerColumns,
|
||||
where.whereSql,
|
||||
'@limit',
|
||||
),
|
||||
)
|
||||
const row = result.recordset?.[0] as ReportEmployeeRow | undefined
|
||||
if (!row) return null
|
||||
|
||||
const normalized = normalizeEmployeeRow(row)
|
||||
;(normalized as any).sourcePayload = {
|
||||
...(normalized.sourcePayload || {}),
|
||||
materialUserId: identity.userId || null,
|
||||
materialUserEmail: clean(identity.email),
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
async function syncPerformanceSurveyEmployeeByIdentityWithContext(
|
||||
pool: any,
|
||||
sql: typeof import('mssql'),
|
||||
schema: Awaited<ReturnType<typeof getReportSchema>>,
|
||||
identity: ReportIdentity,
|
||||
) {
|
||||
const normalized = await fetchNormalizedEmployeeByIdentityWithContext(pool, sql, schema, identity)
|
||||
if (!normalized) return null
|
||||
if (!normalized.dingtalkUserId && !normalized.jobNumber && !normalized.dingEmail) return null
|
||||
return upsertEmployeeProfile(normalized)
|
||||
}
|
||||
|
||||
async function upsertFallbackEmployeeProfileForUser(identity: ReportIdentity & { userId?: string }) {
|
||||
const existing = await findExistingEmployeeProfile({
|
||||
dingtalkUserId: clean(identity.dingtalkUserId),
|
||||
dingEmail: clean(identity.email),
|
||||
})
|
||||
const data = {
|
||||
dingtalkUserId: clean(identity.dingtalkUserId),
|
||||
dingEmail: clean(identity.email),
|
||||
displayName: clean(identity.flowerName) || clean(identity.displayName) || clean(identity.realName) || clean(identity.username) || '未知员工',
|
||||
realName: clean(identity.realName) || clean(identity.username),
|
||||
flowerName: clean(identity.flowerName) || null,
|
||||
departmentName: clean(identity.department),
|
||||
departmentPath: clean(identity.department),
|
||||
sourcePayload: { source: 'material_user_fallback', userId: identity.userId || null },
|
||||
syncedAt: new Date(),
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
return prisma.performanceSurveyEmployeeProfile.update({
|
||||
where: { id: existing.id },
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
return prisma.performanceSurveyEmployeeProfile.create({
|
||||
data: {
|
||||
id: randomUUID(),
|
||||
...data,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function syncPerformanceSurveyEmployeeByIdentity(identity: ReportIdentity) {
|
||||
if (!isPerformanceSurveyReportDbConfigured()) return null
|
||||
|
||||
const { sql, pool } = await getReportPool()
|
||||
try {
|
||||
const schema = await getReportSchema(pool)
|
||||
const where = buildIdentityWhere(
|
||||
schema.employeeColumns,
|
||||
schema.registerColumns,
|
||||
schema.flowerColumns,
|
||||
identity,
|
||||
)
|
||||
if (where.whereSql === '1 = 0') return null
|
||||
|
||||
const request = pool.request().input('limit', sql.Int, 1)
|
||||
if (clean(identity.dingtalkUserId)) request.input('dingtalkUserId', sql.NVarChar(200), clean(identity.dingtalkUserId))
|
||||
if (clean(identity.email)) request.input('email', sql.NVarChar(320), clean(identity.email))
|
||||
if (where.nameParam) request.input('name', sql.NVarChar(200), where.nameParam)
|
||||
|
||||
const result = await request.query<ReportEmployeeRow>(
|
||||
buildSelectSql(
|
||||
schema.employeeColumns,
|
||||
schema.registerColumns,
|
||||
schema.flowerColumns,
|
||||
where.whereSql,
|
||||
'@limit',
|
||||
),
|
||||
)
|
||||
const row = result.recordset?.[0]
|
||||
if (!row) return null
|
||||
|
||||
const normalized = normalizeEmployeeRow(row)
|
||||
if (!normalized.dingtalkUserId && !normalized.jobNumber && !normalized.dingEmail) return null
|
||||
const profile = await upsertEmployeeProfile(normalized)
|
||||
await upsertDepartments()
|
||||
const profile = await syncPerformanceSurveyEmployeeByIdentityWithContext(pool, sql, schema, identity)
|
||||
if (profile) await upsertDepartments()
|
||||
return profile
|
||||
} finally {
|
||||
await pool.close()
|
||||
@ -817,30 +1006,7 @@ export async function previewPerformanceSurveyEmployeeByIdentity(identity: Repor
|
||||
const { sql, pool } = await getReportPool()
|
||||
try {
|
||||
const schema = await getReportSchema(pool)
|
||||
const where = buildIdentityWhere(
|
||||
schema.employeeColumns,
|
||||
schema.registerColumns,
|
||||
schema.flowerColumns,
|
||||
identity,
|
||||
)
|
||||
if (where.whereSql === '1 = 0') return null
|
||||
|
||||
const request = pool.request().input('limit', sql.Int, 1)
|
||||
if (clean(identity.dingtalkUserId)) request.input('dingtalkUserId', sql.NVarChar(200), clean(identity.dingtalkUserId))
|
||||
if (clean(identity.email)) request.input('email', sql.NVarChar(320), clean(identity.email))
|
||||
if (where.nameParam) request.input('name', sql.NVarChar(200), where.nameParam)
|
||||
|
||||
const result = await request.query<ReportEmployeeRow>(
|
||||
buildSelectSql(
|
||||
schema.employeeColumns,
|
||||
schema.registerColumns,
|
||||
schema.flowerColumns,
|
||||
where.whereSql,
|
||||
'@limit',
|
||||
),
|
||||
)
|
||||
const row = result.recordset?.[0]
|
||||
return row ? normalizeEmployeeRow(row) : null
|
||||
return fetchNormalizedEmployeeByIdentityWithContext(pool, sql, schema, identity)
|
||||
} finally {
|
||||
await pool.close()
|
||||
}
|
||||
@ -862,19 +1028,7 @@ export async function ensurePerformanceSurveyEmployeeProfileForUser(identity: Re
|
||||
}
|
||||
|
||||
if (existing) return existing
|
||||
|
||||
const fallbackData = {
|
||||
id: randomUUID(),
|
||||
dingtalkUserId: clean(identity.dingtalkUserId),
|
||||
dingEmail: clean(identity.email),
|
||||
displayName: clean(identity.flowerName) || clean(identity.displayName) || clean(identity.realName) || clean(identity.username) || '未知员工',
|
||||
realName: clean(identity.realName) || clean(identity.username),
|
||||
flowerName: clean(identity.flowerName) || null,
|
||||
sourcePayload: { source: 'material_user_fallback', userId: identity.userId || null },
|
||||
syncedAt: new Date(),
|
||||
}
|
||||
|
||||
return prisma.performanceSurveyEmployeeProfile.create({ data: fallbackData })
|
||||
return upsertFallbackEmployeeProfileForUser(identity)
|
||||
}
|
||||
|
||||
export function ensurePerformanceSurveyDailyEmployeeSyncStarted() {
|
||||
@ -904,29 +1058,67 @@ export function ensurePerformanceSurveyDailyEmployeeSyncStarted() {
|
||||
|
||||
export async function syncPerformanceSurveyEmployees(options?: { limit?: number }) {
|
||||
const limit = Math.max(1, Math.min(Number(options?.limit || 3000), 10_000))
|
||||
const identities = await listMaterialAccountSyncIdentities(limit)
|
||||
|
||||
if (!isPerformanceSurveyReportDbConfigured()) {
|
||||
for (const identity of identities) {
|
||||
await upsertFallbackEmployeeProfileForUser(identity)
|
||||
}
|
||||
const departmentCount = await upsertDepartments()
|
||||
return {
|
||||
scannedAccounts: identities.length,
|
||||
upserted: 0,
|
||||
fallback: identities.length,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
departmentCount,
|
||||
syncedAt: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
const { sql, pool } = await getReportPool()
|
||||
|
||||
try {
|
||||
const schema = await getReportSchema(pool)
|
||||
const rows = await fetchFullSyncRows(pool, sql, schema, limit)
|
||||
|
||||
let upserted = 0
|
||||
let fallback = 0
|
||||
let skipped = 0
|
||||
for (const row of rows) {
|
||||
const normalized = normalizeEmployeeRow(row)
|
||||
if (!normalized.dingtalkUserId && !normalized.jobNumber && !normalized.dingEmail) {
|
||||
let failed = 0
|
||||
for (const identity of identities) {
|
||||
if (!clean(identity.dingtalkUserId) && !clean(identity.email) && !clean(identity.username) && !clean(identity.realName)) {
|
||||
skipped += 1
|
||||
continue
|
||||
}
|
||||
await upsertEmployeeProfile(normalized)
|
||||
upserted += 1
|
||||
|
||||
try {
|
||||
const synced = await syncPerformanceSurveyEmployeeByIdentityWithContext(pool, sql, schema, identity)
|
||||
if (synced) {
|
||||
upserted += 1
|
||||
} else {
|
||||
await upsertFallbackEmployeeProfileForUser(identity)
|
||||
fallback += 1
|
||||
}
|
||||
} catch (error) {
|
||||
failed += 1
|
||||
console.warn('[performance-survey] 单个人员档案同步失败,使用素材库账号兜底:', {
|
||||
userId: identity.userId,
|
||||
email: identity.email,
|
||||
error,
|
||||
})
|
||||
await upsertFallbackEmployeeProfileForUser(identity)
|
||||
fallback += 1
|
||||
}
|
||||
}
|
||||
|
||||
const departmentCount = await upsertDepartments()
|
||||
|
||||
return {
|
||||
scannedAccounts: identities.length,
|
||||
upserted,
|
||||
fallback,
|
||||
skipped,
|
||||
failed,
|
||||
departmentCount,
|
||||
syncedAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
import {
|
||||
ensurePerformanceSurveyDailyEmployeeSyncStarted,
|
||||
ensurePerformanceSurveyEmployeeProfileForUser,
|
||||
getPerformanceSurveyAccountProfileWhere,
|
||||
syncPerformanceSurveyEmployees,
|
||||
} from '@/lib/performance-survey-report-sync'
|
||||
|
||||
@ -78,26 +79,64 @@ function decodeDepartmentPart(value: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function getDepartmentPathValue(departmentName: string, departmentPath: string) {
|
||||
return `${DEPARTMENT_PATH_VALUE_PREFIX}${encodeDepartmentPart(departmentName)}|${encodeDepartmentPart(departmentPath)}`
|
||||
function parseDepartmentPathParts(value?: string | null) {
|
||||
const normalized = clean(value)
|
||||
if (!normalized) return []
|
||||
if (normalized.startsWith('[')) {
|
||||
try {
|
||||
const parsed = JSON.parse(normalized)
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed.map((item) => clean(item)).filter((item): item is string => Boolean(item))
|
||||
}
|
||||
} catch {
|
||||
// 继续按普通分隔符拆分。
|
||||
}
|
||||
}
|
||||
return normalized
|
||||
.split(/[/>||,,;;]/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function getDepartmentNodeId(departmentName: string) {
|
||||
return `dept:${encodeDepartmentPart(departmentName)}`
|
||||
function getDepartmentPathValue(departmentPath: string) {
|
||||
return `${DEPARTMENT_PATH_VALUE_PREFIX}${encodeDepartmentPart(departmentPath)}`
|
||||
}
|
||||
|
||||
function getDepartmentPathNodeId(departmentName: string, departmentPath: string) {
|
||||
return `deptPath:${encodeDepartmentPart(departmentName)}|${encodeDepartmentPart(departmentPath)}`
|
||||
function getDepartmentNodeId(departmentPath: string) {
|
||||
return `dept:${encodeDepartmentPart(departmentPath)}`
|
||||
}
|
||||
|
||||
function getScopeDepartmentWhere(scopeDepartment: string) {
|
||||
return {
|
||||
OR: [
|
||||
{ departmentName: scopeDepartment },
|
||||
{ departmentPath: scopeDepartment },
|
||||
{ departmentPath: { startsWith: `${scopeDepartment}/` } },
|
||||
{ departmentPath: { contains: `/${scopeDepartment}/` } },
|
||||
{ departmentPath: { endsWith: `/${scopeDepartment}` } },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
function parseDepartmentSelection(value: string) {
|
||||
if (value.startsWith(DEPARTMENT_PATH_VALUE_PREFIX)) {
|
||||
const payload = value.slice(DEPARTMENT_PATH_VALUE_PREFIX.length)
|
||||
const [departmentNameRaw, departmentPathRaw] = payload.split('|')
|
||||
const departmentName = clean(decodeDepartmentPart(departmentNameRaw || ''))
|
||||
const departmentPath = clean(decodeDepartmentPart(departmentPathRaw || ''))
|
||||
if (departmentName && departmentPath) {
|
||||
return { departmentName, departmentPath }
|
||||
const decoded = decodeDepartmentPart(payload)
|
||||
if (decoded.includes('|')) {
|
||||
const [departmentNameRaw, departmentPathRaw] = decoded.split('|')
|
||||
const departmentName = clean(departmentNameRaw)
|
||||
const departmentPath = clean(departmentPathRaw)
|
||||
if (departmentName && departmentPath) {
|
||||
return { departmentName, departmentPath }
|
||||
}
|
||||
}
|
||||
const departmentPath = clean(decoded)
|
||||
if (departmentPath) {
|
||||
const parts = parseDepartmentPathParts(departmentPath)
|
||||
return {
|
||||
departmentName: parts[parts.length - 1] || departmentPath,
|
||||
departmentPath,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,9 +154,10 @@ function getDepartmentSelectionWhere(values: string[]) {
|
||||
|
||||
if (parsed.departmentPath) {
|
||||
departmentPathClauses.push({
|
||||
AND: [
|
||||
{ departmentName: parsed.departmentName },
|
||||
OR: [
|
||||
{ departmentPath: parsed.departmentPath },
|
||||
{ departmentPath: { startsWith: `${parsed.departmentPath}/` } },
|
||||
...(parsed.departmentPath.includes('/') ? [] : [{ departmentName: parsed.departmentName }]),
|
||||
],
|
||||
})
|
||||
} else {
|
||||
@ -306,7 +346,8 @@ export async function searchPerformanceSurveyEmployees(session: Session | null,
|
||||
const department = clean(params.get('department'))
|
||||
const limit = Math.max(1, Math.min(Number(params.get('limit') || 50), 200))
|
||||
|
||||
const and: any[] = []
|
||||
const accountWhere = await getPerformanceSurveyAccountProfileWhere()
|
||||
const and: any[] = [accountWhere]
|
||||
if (department) {
|
||||
const departmentClauses = getDepartmentSelectionWhere([department])
|
||||
if (departmentClauses.length === 1) {
|
||||
@ -316,7 +357,7 @@ export async function searchPerformanceSurveyEmployees(session: Session | null,
|
||||
}
|
||||
}
|
||||
if (!ctx.isSystemAdmin && ctx.scopeDepartment) {
|
||||
and.push({ departmentName: ctx.scopeDepartment })
|
||||
and.push(getScopeDepartmentWhere(ctx.scopeDepartment))
|
||||
}
|
||||
if (search) {
|
||||
and.push({ OR: [
|
||||
@ -332,7 +373,7 @@ export async function searchPerformanceSurveyEmployees(session: Session | null,
|
||||
{ managerName: { contains: search, mode: 'insensitive' } },
|
||||
] })
|
||||
}
|
||||
const where: any = and.length > 0 ? { AND: and } : {}
|
||||
const where: any = { AND: and }
|
||||
|
||||
const employees = await prisma.performanceSurveyEmployeeProfile.findMany({
|
||||
where,
|
||||
@ -347,120 +388,73 @@ export async function listPerformanceSurveyDepartments(session: Session | null)
|
||||
const ctx = await getPerformanceSurveyContext(session)
|
||||
ensureManager(ctx)
|
||||
|
||||
const where: any =
|
||||
!ctx.isSystemAdmin && ctx.scopeDepartment
|
||||
? { departmentName: ctx.scopeDepartment }
|
||||
: { departmentName: { not: null } }
|
||||
const accountWhere = await getPerformanceSurveyAccountProfileWhere()
|
||||
const and: any[] = [
|
||||
accountWhere,
|
||||
{ departmentName: { not: null } },
|
||||
]
|
||||
if (!ctx.isSystemAdmin && ctx.scopeDepartment) {
|
||||
and.push(getScopeDepartmentWhere(ctx.scopeDepartment))
|
||||
}
|
||||
|
||||
const employees = await prisma.performanceSurveyEmployeeProfile.findMany({
|
||||
where,
|
||||
where: { AND: and },
|
||||
select: {
|
||||
departmentName: true,
|
||||
departmentPath: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (employees.length > 0) {
|
||||
const departments = new Map<string, {
|
||||
name: string
|
||||
employeeCount: number
|
||||
children: Map<string, number>
|
||||
}>()
|
||||
const nodeByPath = new Map<string, any>()
|
||||
for (const employee of employees) {
|
||||
const departmentName = clean(employee.departmentName)
|
||||
const pathParts = parseDepartmentPathParts(employee.departmentPath)
|
||||
const parts = pathParts.length > 0 ? pathParts : departmentName ? [departmentName] : []
|
||||
if (parts.length === 0) continue
|
||||
|
||||
for (const employee of employees) {
|
||||
const departmentName = clean(employee.departmentName)
|
||||
if (!departmentName) continue
|
||||
|
||||
const existing = departments.get(departmentName) || {
|
||||
name: departmentName,
|
||||
employeeCount: 0,
|
||||
children: new Map<string, number>(),
|
||||
}
|
||||
existing.employeeCount += 1
|
||||
|
||||
const departmentPath = clean(employee.departmentPath)
|
||||
if (departmentPath && departmentPath !== departmentName) {
|
||||
existing.children.set(departmentPath, (existing.children.get(departmentPath) || 0) + 1)
|
||||
for (let index = 0; index < parts.length; index += 1) {
|
||||
const fullPath = parts.slice(0, index + 1).join('/')
|
||||
const parentPath = index > 0 ? parts.slice(0, index).join('/') : null
|
||||
const existing = nodeByPath.get(fullPath)
|
||||
if (existing) {
|
||||
existing.employeeCount += 1
|
||||
continue
|
||||
}
|
||||
|
||||
departments.set(departmentName, existing)
|
||||
nodeByPath.set(fullPath, {
|
||||
id: getDepartmentNodeId(fullPath),
|
||||
name: parts[index],
|
||||
parentId: parentPath ? getDepartmentNodeId(parentPath) : ALL_DEPARTMENTS_NODE_ID,
|
||||
parentName: parentPath ? parts[index - 1] : ALL_DEPARTMENTS_NODE_NAME,
|
||||
employeeCount: 1,
|
||||
selectable: true,
|
||||
value: getDepartmentPathValue(fullPath),
|
||||
departmentName: parts[index],
|
||||
departmentPath: fullPath,
|
||||
fullPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const sortedDepartments = Array.from(departments.values()).sort((a, b) => {
|
||||
if (b.employeeCount !== a.employeeCount) return b.employeeCount - a.employeeCount
|
||||
return a.name.localeCompare(b.name, 'zh-CN')
|
||||
})
|
||||
|
||||
const totalEmployees = sortedDepartments.reduce((sum, item) => sum + item.employeeCount, 0)
|
||||
const nodes: any[] = [{
|
||||
return [
|
||||
{
|
||||
id: ALL_DEPARTMENTS_NODE_ID,
|
||||
name: ALL_DEPARTMENTS_NODE_NAME,
|
||||
parentId: null,
|
||||
parentName: null,
|
||||
employeeCount: totalEmployees,
|
||||
employeeCount: employees.length,
|
||||
selectable: false,
|
||||
value: null,
|
||||
}]
|
||||
|
||||
for (const department of sortedDepartments) {
|
||||
const departmentId = getDepartmentNodeId(department.name)
|
||||
nodes.push({
|
||||
id: departmentId,
|
||||
name: department.name,
|
||||
parentId: ALL_DEPARTMENTS_NODE_ID,
|
||||
parentName: ALL_DEPARTMENTS_NODE_NAME,
|
||||
employeeCount: department.employeeCount,
|
||||
selectable: true,
|
||||
value: department.name,
|
||||
})
|
||||
|
||||
const children = Array.from(department.children.entries()).sort((a, b) => {
|
||||
if (b[1] !== a[1]) return b[1] - a[1]
|
||||
return a[0].localeCompare(b[0], 'zh-CN')
|
||||
})
|
||||
for (const [departmentPath, employeeCount] of children) {
|
||||
nodes.push({
|
||||
id: getDepartmentPathNodeId(department.name, departmentPath),
|
||||
name: departmentPath,
|
||||
parentId: departmentId,
|
||||
parentName: department.name,
|
||||
employeeCount,
|
||||
selectable: true,
|
||||
value: getDepartmentPathValue(department.name, departmentPath),
|
||||
departmentName: department.name,
|
||||
departmentPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
const grouped = await prisma.performanceSurveyEmployeeProfile.groupBy({
|
||||
by: ['departmentName'],
|
||||
where: {
|
||||
departmentName: ctx.isSystemAdmin
|
||||
? { not: null }
|
||||
: ctx.scopeDepartment
|
||||
? ctx.scopeDepartment
|
||||
: { not: null },
|
||||
} as any,
|
||||
_count: { _all: true },
|
||||
orderBy: { departmentName: 'asc' },
|
||||
})
|
||||
|
||||
return grouped
|
||||
.filter((item) => item.departmentName)
|
||||
.map((item) => ({
|
||||
id: item.departmentName!,
|
||||
name: item.departmentName!,
|
||||
parentName: null,
|
||||
parentId: null,
|
||||
employeeCount: item._count._all,
|
||||
syncedAt: null,
|
||||
selectable: true,
|
||||
value: item.departmentName!,
|
||||
}))
|
||||
departmentPath: null,
|
||||
fullPath: null,
|
||||
},
|
||||
...Array.from(nodeByPath.values()).sort((a, b) => {
|
||||
const depthDiff = parseDepartmentPathParts(a.departmentPath).length - parseDepartmentPathParts(b.departmentPath).length
|
||||
if (depthDiff !== 0) return depthDiff
|
||||
if (b.employeeCount !== a.employeeCount) return b.employeeCount - a.employeeCount
|
||||
return a.fullPath.localeCompare(b.fullPath, 'zh-CN')
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
export async function listPerformanceSurveys(session: Session | null) {
|
||||
@ -1093,7 +1087,12 @@ async function resolveSurveyTargets(body: any, surveyKind: string) {
|
||||
const employeeTargets =
|
||||
targetEmployeeIds.length > 0
|
||||
? await prisma.performanceSurveyEmployeeProfile.findMany({
|
||||
where: { id: { in: targetEmployeeIds } },
|
||||
where: {
|
||||
AND: [
|
||||
await getPerformanceSurveyAccountProfileWhere(),
|
||||
{ id: { in: targetEmployeeIds } },
|
||||
],
|
||||
},
|
||||
})
|
||||
: []
|
||||
|
||||
@ -1130,31 +1129,39 @@ async function resolveSurveyEvaluators(body: any, ctx: UserContext) {
|
||||
or.push({ id: ctx.employee.id })
|
||||
}
|
||||
|
||||
const where: any = { OR: or }
|
||||
const and: any[] = [
|
||||
await getPerformanceSurveyAccountProfileWhere(),
|
||||
{ OR: or },
|
||||
]
|
||||
if (!ctx.isSystemAdmin && ctx.scopeDepartment) {
|
||||
where.departmentName = ctx.scopeDepartment
|
||||
and.push(getScopeDepartmentWhere(ctx.scopeDepartment))
|
||||
}
|
||||
const where: any = { AND: and }
|
||||
|
||||
const employees = or.length > 0
|
||||
? await prisma.performanceSurveyEmployeeProfile.findMany({ where, orderBy: { displayName: 'asc' } })
|
||||
: []
|
||||
|
||||
const usersByDing = employees.length > 0
|
||||
const dingtalkUserIds = employees.map((employee) => clean(employee.dingtalkUserId)).filter((item): item is string => Boolean(item))
|
||||
const dingEmails = employees.map((employee) => clean(employee.dingEmail)).filter((item): item is string => Boolean(item))
|
||||
const userLookupOr = [
|
||||
...(dingtalkUserIds.length > 0 ? [{ dingtalkUserId: { in: dingtalkUserIds } }] : []),
|
||||
...(dingEmails.length > 0 ? [{ email: { in: dingEmails } }] : []),
|
||||
]
|
||||
const users = userLookupOr.length > 0
|
||||
? await prisma.user.findMany({
|
||||
where: {
|
||||
dingtalkUserId: {
|
||||
in: employees.map((employee) => employee.dingtalkUserId).filter((item): item is string => Boolean(item)),
|
||||
},
|
||||
},
|
||||
select: { id: true, dingtalkUserId: true },
|
||||
where: { OR: userLookupOr },
|
||||
select: { id: true, dingtalkUserId: true, email: true },
|
||||
})
|
||||
: []
|
||||
const userIdByDing = new Map(usersByDing.map((user) => [user.dingtalkUserId, user.id]))
|
||||
const userIdByDing = new Map(users.map((user) => [user.dingtalkUserId, user.id]).filter(([dingtalkUserId]) => Boolean(dingtalkUserId)) as Array<[string, string]>)
|
||||
const userIdByEmail = new Map(users.map((user) => [user.email, user.id]))
|
||||
|
||||
const deduped = new Map<string, any>()
|
||||
for (const employee of employees) {
|
||||
const dingtalkUserId = employee.dingtalkUserId || null
|
||||
const userId = dingtalkUserId ? userIdByDing.get(dingtalkUserId) || null : null
|
||||
const userId = (dingtalkUserId ? userIdByDing.get(dingtalkUserId) || null : null)
|
||||
|| (employee.dingEmail ? userIdByEmail.get(employee.dingEmail) || null : null)
|
||||
const evaluatorKey = getEvaluatorKey({
|
||||
employeeId: employee.id,
|
||||
userId,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user