Files
daiyongkang 76f266645d Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:04:03 +00:00

142 lines
4.7 KiB
TypeScript

import { PrismaClient } from '@prisma/client';
import * as fs from 'fs';
const prisma = new PrismaClient();
const DUMP = '/opt/import/prod/oa_hr.json';
const ROLES: { code: string; name: string; dataScope: string }[] = [
{ code: 'rd_director', name: '研发总监', dataScope: 'DEPT' },
{ code: '3d_director', name: '三维总监', dataScope: 'DEPT' },
{ code: 'video_director', name: '摄制总监', dataScope: 'DEPT' },
{ code: 'material_director', name: '物资总监', dataScope: 'DEPT' },
{ code: 'pm', name: '项目经理', dataScope: 'DEPT' },
{ code: 'rd_staff', name: '研发技术岗', dataScope: 'SELF' },
{ code: '3d_staff', name: '三维技术岗', dataScope: 'SELF' },
{ code: 'video_staff', name: '摄制技术岗', dataScope: 'SELF' },
{ code: 'material_staff', name: '物资技术岗', dataScope: 'SELF' },
{ code: 'tech', name: '普通岗位', dataScope: 'SELF' },
];
const POST_ROLE: Record<string, string> = {
sysadmin: 'admin',
boss: 'owner',
business_director: 'biz_director',
business_staff: 'biz_staff',
rd_director: 'rd_director',
'3d_director': '3d_director',
video_director: 'video_director',
material_director: 'material_director',
pm: 'pm',
tech: 'tech',
rd_staff: 'rd_staff',
'3d_staff': '3d_staff',
video_staff: 'video_staff',
material_staff: 'material_staff',
};
function pickRole(userName: string, postCodes: string[]) {
if (userName === 'xibei') return 'employee';
if (userName === 'administrator' || userName === 'admin') return 'admin';
const rank: Record<string, number> = {
employee: 0,
tech: 1,
rd_staff: 1,
'3d_staff': 1,
video_staff: 1,
material_staff: 1,
pm: 2,
biz_staff: 3,
rd_director: 4,
'3d_director': 4,
video_director: 4,
material_director: 4,
tech_director: 4,
biz_director: 5,
owner: 6,
admin: 7,
};
let best: string | null = null;
let bestRank = -1;
for (const c of postCodes) {
const role = POST_ROLE[c];
if (!role) continue;
const r = rank[role] ?? 1;
if (r > bestRank) {
best = role;
bestRank = r;
}
}
return best || 'employee';
}
async function main() {
for (const r of ROLES) {
const existing = await prisma.role.findFirst({ where: { code: r.code } });
if (existing) {
await prisma.role.update({ where: { id: existing.id }, data: { name: r.name, dataScope: r.dataScope } });
} else {
await prisma.role.create({ data: { name: r.name, code: r.code, dataScope: r.dataScope } });
}
}
await prisma.role.updateMany({ where: { code: 'biz_staff' }, data: { name: '商务专员' } });
await prisma.role.updateMany({ where: { code: 'biz_director' }, data: { name: '商务总监' } });
await prisma.role.updateMany({ where: { code: 'owner' }, data: { name: '企业负责人' } });
const dump = JSON.parse(fs.readFileSync(DUMP, 'utf8')) as {
posts: { id: number; code: string; name: string }[];
users: { id: number; userName: string; nickName: string }[];
userPosts: { postId: number; userId: number }[];
};
const postById = new Map(dump.posts.map((p) => [p.id, p]));
const postsByUser = new Map<number, { code: string; name: string }[]>();
for (const up of dump.userPosts) {
const post = postById.get(up.postId);
if (!post) continue;
const list = postsByUser.get(up.userId) || [];
list.push(post);
postsByUser.set(up.userId, list);
}
const roles = await prisma.role.findMany();
const roleByCode = new Map(roles.map((r) => [r.code, r.id]));
let n = 0;
for (const u of dump.users) {
if (u.userName === 'administrator') continue;
const user = await prisma.user.findFirst({ where: { username: u.userName } });
if (!user) continue;
const posts = postsByUser.get(u.id) || [];
const roleCode = pickRole(u.userName, posts.map((p) => p.code));
const roleId = roleByCode.get(roleCode);
if (!roleId) continue;
await prisma.userRole.deleteMany({ where: { userId: user.id } });
await prisma.userRole.create({ data: { userId: user.id, roleId } });
const title = posts.find((p) => POST_ROLE[p.code] === roleCode)?.name || posts[0]?.name;
if (title && user.employeeId) {
await prisma.employee.update({ where: { id: user.employeeId }, data: { title } });
}
n += 1;
}
const delQual = await prisma.qualification.deleteMany({ where: { code: 'QUAL_P6_OLD' } });
const cancelled = await prisma.approvalTask.updateMany({
where: { bizType: 'EXPENSE_APPROVAL', status: 'PENDING' },
data: { status: 'CANCELLED' },
});
console.log(
JSON.stringify({
reassignedUsers: n,
deletedTestQuals: delQual.count,
cancelledExpenseApprovals: cancelled.count,
}),
);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => prisma.$disconnect());