diff --git a/scripts/fix-liuwei-jiangcheng.ts b/scripts/fix-liuwei-jiangcheng.ts new file mode 100644 index 00000000..5a531a1f --- /dev/null +++ b/scripts/fix-liuwei-jiangcheng.ts @@ -0,0 +1,112 @@ +/** + * 处置:刘伟/江澄账号串档修复(事务,可回滚) + * + * 背景:花名册"刘伟(花名江澄,工号JS19071901,M1,品类六部运营主管)"的档案 + * 被素材库"刘伟"账号(外部交流,钉钉ID 36135934671751)错误绑定; + * 素材库"江澄"账号(品类六部正主,钉钉ID 1617610635671751)反而无绑定。 + * + * 处置(用户确认): + * 1. 硬删除素材库"刘伟"账号(无素材/无日志,影响已评估为零) + * 2. 档案 6eb6fd4e(M1/JS19071901)部门修正为品类六部 + * 3. 建立"江澄"账号 ↔ 该档案的活跃绑定(MANUAL_VERIFIED) + * 4. "江澄"账号角色升为 DEPT_ADMIN(M1 负责人) + * + * 用法:npx tsx --env-file=.env scripts/fix-liuwei-jiangcheng.ts --apply + * 不加 --apply 为预演 + */ +import { PrismaClient } from '@prisma/client' +const prisma = new PrismaClient() + +const APPLY = process.argv.includes('--apply') +const LIUWEI_USER_ID = 'f8fe5ab5-29ab-44c9-833d-7a7247f39d7d' +const JIANGCHENG_USER_ID = 'da2f7fc3-8453-4ae4-a61c-7fc060ed7a49' +const PROFILE_ID = '6eb6fd4e-6cdd-43ca-8697-7f48a116171a' +const CATEGORY6_DEPT_PATH = '江苏简尚品牌管理有限公司/运营中心A/事业三部/品类六部' + +;(async () => { + console.log(APPLY ? '=== 实际执行模式 ===' : '=== 预演模式(不写入)===') + + // 预检:确认关键对象存在 + const liuwei = await prisma.user.findUnique({ where: { id: LIUWEI_USER_ID } }) + const jiangcheng = await prisma.user.findUnique({ where: { id: JIANGCHENG_USER_ID } }) + const profile = await prisma.performanceSurveyEmployeeProfile.findUnique({ where: { id: PROFILE_ID } }) + if (!liuwei) throw new Error('刘伟账号不存在') + if (!jiangcheng) throw new Error('江澄账号不存在') + if (!profile) throw new Error('品类六部档案不存在') + + console.log(`刘伟账号: ${liuwei.username} (${liuwei.dingtalkUserId})`) + console.log(`江澄账号: ${jiangcheng.username} (${jiangcheng.dingtalkUserId})`) + console.log(`档案: ${profile.displayName} | 当前部门: ${profile.departmentPath} | 等级: ${profile.positionLevel}(${profile.levelType})`) + console.log('') + + // 预检:刘伟账号必须无素材/无日志 + const mats = await prisma.material.count({ where: { userId: LIUWEI_USER_ID } }) + const logs = await prisma.materialDownloadLog.count({ where: { userId: LIUWEI_USER_ID } }).catch(() => 0) + if (mats > 0 || logs > 0) { + throw new Error(`刘伟账号还有数据(素材${mats}/日志${logs}),拒绝删除,请人工检查`) + } + console.log(`安全预检通过:刘伟账号素材=${mats} 日志=${logs}`) + + if (!APPLY) { + console.log('\n[预演] 将执行:') + console.log(' 1. 删除刘伟账号(含其绑定)') + console.log(` 2. 档案 ${PROFILE_ID} 部门 → ${CATEGORY6_DEPT_PATH}`) + console.log(' 3. 江澄账号 ↔ 档案 建立 ACTIVE 绑定(MANUAL_VERIFIED)') + console.log(' 4. 江澄账号角色 → DEPT_ADMIN') + console.log('\n(未写入任何数据,加 --apply 执行)') + await prisma.$disconnect() + return + } + + await prisma.$transaction(async (tx) => { + // 1. 删除刘伟账号(级联删绑定;素材/日志已确认为 0) + await tx.userEmployeeBinding.deleteMany({ where: { userId: LIUWEI_USER_ID } }) + await tx.user.delete({ where: { id: LIUWEI_USER_ID } }) + console.log('✓ 已删除刘伟账号及其绑定') + + // 2. 档案部门修正为品类六部 + await tx.performanceSurveyEmployeeProfile.update({ + where: { id: PROFILE_ID }, + data: { + departmentPath: CATEGORY6_DEPT_PATH, + departmentName: '品类六部', + }, + }) + console.log(`✓ 档案部门已修正: ${CATEGORY6_DEPT_PATH}`) + + // 3. 江澄账号 ↔ 档案 绑定 + await tx.userEmployeeBinding.create({ + data: { + userId: JIANGCHENG_USER_ID, + employeeProfileId: PROFILE_ID, + bindingSource: 'MANUAL_VERIFIED', + status: 'ACTIVE', + verifiedAt: new Date(), + note: '人工处置:刘伟/江澄同名串档修复(2026-08-14)', + }, + }) + console.log('✓ 江澄账号已建立绑定') + + // 4. 江澄角色升 DEPT_ADMIN + await tx.user.update({ + where: { id: JIANGCHENG_USER_ID }, + data: { role: 'DEPT_ADMIN' }, + }) + console.log('✓ 江澄账号角色 → DEPT_ADMIN') + }) + + // 验证 + const verify = await prisma.user.findUnique({ + where: { id: JIANGCHENG_USER_ID }, + select: { + username: true, role: true, + employeeBindings: { where: { status: 'ACTIVE' }, select: { employeeProfile: { select: { displayName: true, departmentPath: true, positionLevel: true, levelType: true } } } }, + }, + }) + console.log('\n=== 验证 ===') + console.log(JSON.stringify(verify, null, 2)) + const liuweiAfter = await prisma.user.findUnique({ where: { id: LIUWEI_USER_ID } }).catch(() => null) + console.log(`刘伟账号删除后存在: ${Boolean(liuweiAfter)}`) + + await prisma.$disconnect() +})().catch((e) => { console.error(e); process.exit(1) })