Files
Public/apps/api/prisma/seed.ts
T
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

384 lines
15 KiB
TypeScript

import { PrismaClient } from '@prisma/client';
import * as argon2 from 'argon2';
import { MENUS, type MenuNode } from './menus';
const prisma = new PrismaClient();
async function main() {
const passwordHash = await argon2.hash('Demo@123456');
const adminHash = await argon2.hash('Admin@123456');
await prisma.$transaction(async (tx) => {
await tx.operationLog.deleteMany();
await tx.paymentRecord.deleteMany();
await tx.projectChange.deleteMany();
await tx.sealItem.deleteMany();
await tx.numberRule.deleteMany();
await tx.todoTask.deleteMany();
await tx.approvalTask.deleteMany();
await tx.workReport.deleteMany();
await tx.payrollItem.deleteMany();
await tx.payrollRun.deleteMany();
await tx.attendanceMonth.deleteMany();
await tx.employmentEvent.deleteMany();
await tx.expenseClaim.deleteMany();
await tx.loanRecord.deleteMany();
await tx.bondRecord.deleteMany();
await tx.invoiceRecord.deleteMany();
await tx.timesheet.deleteMany();
await tx.projectAcceptance.deleteMany();
await tx.projectBudget.deleteMany();
await tx.projectTask.deleteMany();
await tx.purchaseRequest.deleteMany();
await tx.assetOccupancy.deleteMany();
await tx.contractReviewer.deleteMany();
await tx.contractActionLog.deleteMany();
await tx.paymentMilestone.deleteMany();
await tx.project.deleteMany();
await tx.sealRequest.deleteMany();
await tx.contract.deleteMany();
await tx.bidDocVersion.deleteMany();
await tx.bidAssignee.deleteMany();
await tx.bidActionLog.deleteMany();
await tx.bidReviewer.deleteMany();
await tx.credentialBorrow.deleteMany();
await tx.bidCase.deleteMany();
await tx.partyContact.deleteMany();
await tx.qualification.deleteMany();
await tx.userRole.deleteMany();
await tx.rolePermission.deleteMany();
await tx.refreshToken.deleteMany();
await tx.user.deleteMany();
await tx.employee.deleteMany();
await tx.department.deleteMany();
await tx.permission.deleteMany();
await tx.role.deleteMany();
await tx.dictItem.deleteMany();
await tx.attendanceRule.deleteMany();
await tx.hrSetting.deleteMany();
await tx.legalEntity.deleteMany();
await tx.businessParty.deleteMany();
const root = await tx.department.create({
data: {
name: '演示科技有限公司',
code: 'ROOT',
deptType: 'ADMIN',
sortNo: 0,
},
});
const depts = await Promise.all(
[
['商务部', 'BIZ', 'BUSINESS', null, 10],
['三维部门', 'TECH_3D', 'TECH', 'CAT_3D', 20],
['摄制部门', 'TECH_FILM', 'TECH', 'CAT_FILM', 30],
['物资部门', 'TECH_MATERIAL', 'TECH', 'CAT_MATERIAL', 40],
['软件研发部门', 'TECH_SOFTWARE', 'TECH', 'CAT_SOFTWARE', 50],
['财务部', 'FIN', 'FINANCE', null, 60],
['人事行政', 'HR', 'HR', null, 70],
].map(([name, code, deptType, categoryCode, sortNo]) =>
tx.department.create({
data: {
parentId: root.id,
name: name as string,
code: code as string,
deptType: deptType as string,
categoryCode: categoryCode as string | null,
sortNo: sortNo as number,
},
}),
),
);
const byCode = Object.fromEntries(depts.map((d) => [d.code, d]));
const permIds: Record<string, string> = {};
const writeMenus = async (nodes: MenuNode[], parentId: string | null, root = true) => {
for (let i = 0; i < nodes.length; i++) {
const menu = nodes[i];
const row = await tx.permission.create({
data: {
parentId,
name: menu.name,
code: menu.code,
permType: 'MENU',
path: menu.path,
icon: menu.icon,
sortNo: root ? (i + 1) * 10 : i + 1,
},
});
permIds[menu.code] = row.id;
if (menu.children?.length) await writeMenus(menu.children, row.id, false);
}
};
await writeMenus(MENUS, null);
const roles = {
admin: await tx.role.create({ data: { name: '系统管理员', code: 'admin', dataScope: 'ALL' } }),
owner: await tx.role.create({ data: { name: '企业负责人', code: 'owner', dataScope: 'ALL' } }),
biz_director: await tx.role.create({ data: { name: '商务总监', code: 'biz_director', dataScope: 'DEPT' } }),
biz_staff: await tx.role.create({ data: { name: '商务专员', code: 'biz_staff', dataScope: 'SELF' } }),
tech_director: await tx.role.create({ data: { name: '技术总监', code: 'tech_director', dataScope: 'DEPT' } }),
finance: await tx.role.create({ data: { name: '财务人员', code: 'finance', dataScope: 'ALL' } }),
hr: await tx.role.create({ data: { name: '人事', code: 'hr', dataScope: 'ALL' } }),
employee: await tx.role.create({ data: { name: '员工', code: 'employee', dataScope: 'SELF' } }),
};
const allPerms = await tx.permission.findMany();
const grant = async (roleId: string, codes: string[]) => {
const selected =
codes[0] === '*'
? allPerms
: allPerms.filter((p) => codes.some((c) => p.code === c || p.code.startsWith(c)));
await tx.rolePermission.createMany({
data: selected.map((p) => ({ roleId, permissionId: p.id })),
skipDuplicates: true,
});
};
await grant(roles.admin.id, ['*']);
await grant(roles.owner.id, ['*']);
await grant(roles.biz_director.id, ['office', 'party', 'seal', 'bid', 'contract', 'finance', 'report', 'hr:performance']);
await grant(roles.biz_staff.id, ['office', 'party', 'seal', 'bid', 'contract']);
await grant(roles.tech_director.id, ['office', 'bid', 'project', 'report', 'hr:performance']);
await grant(roles.finance.id, ['office', 'finance', 'contract', 'report', 'bid:result']);
await grant(roles.hr.id, ['office', 'hr', 'system:hr', 'system:org', 'system:attendance-rules']);
await grant(roles.employee.id, ['office', 'project:timesheets']);
const mkEmp = (
name: string,
no: string,
deptCode: string,
title: string,
rest: string,
baseSalary: number,
perfBase: number,
socialBase: number,
) =>
tx.employee.create({
data: {
name,
employeeNo: no,
departmentId: byCode[deptCode].id,
title,
restSchedule: rest,
employmentStatus: 'REGULAR',
hiredAt: new Date('2022-01-01'),
baseSalary,
perfBase,
socialBase,
},
});
const emps = {
admin: await mkEmp('系统管理员', 'E000', 'HR', '管理员', 'DOUBLE', 8000, 0, 8000),
owner: await mkEmp('陈总', 'E001', 'BIZ', '企业负责人', 'SINGLE', 25000, 10000, 20000),
bizdir: await mkEmp('王商务', 'E010', 'BIZ', '商务总监', 'SINGLE', 18000, 8000, 18000),
bizstaff: await mkEmp('李专员', 'E011', 'BIZ', '商务专员', 'SINGLE', 9000, 2500, 9000),
dir3d: await mkEmp('赵三维', 'E020', 'TECH_3D', '三维总监', 'SINGLE', 15000, 5000, 15000),
dirfilm: await mkEmp('孙摄制', 'E021', 'TECH_FILM', '摄制总监', 'SINGLE', 15000, 5000, 15000),
dirmaterial: await mkEmp('周物资', 'E022', 'TECH_MATERIAL', '物资总监', 'SINGLE', 15000, 5000, 15000),
dirsoft: await mkEmp('吴软件', 'E023', 'TECH_SOFTWARE', '软件总监', 'DOUBLE', 15000, 5000, 15000),
finance: await mkEmp('郑财务', 'E030', 'FIN', '会计', 'SINGLE', 10000, 2000, 10000),
hr: await mkEmp('冯人事', 'E040', 'HR', '人事专员', 'SINGLE', 9000, 2000, 9000),
employee: await mkEmp('钱员工', 'E100', 'TECH_SOFTWARE', '工程师', 'DOUBLE', 10000, 3000, 10000),
};
const softpm = await mkEmp('软件项目经理', 'E101', 'TECH_SOFTWARE', '项目经理', 'DOUBLE', 12000, 4000, 12000);
await tx.employee.update({
where: { id: emps.employee.id },
data: { managerId: softpm.id },
});
await tx.employee.update({
where: { id: softpm.id },
data: { managerId: emps.dirsoft.id },
});
const mkUser = async (
username: string,
hash: string,
emp: { id: string; name: string },
roleId: string,
) => {
const u = await tx.user.create({
data: {
username,
passwordHash: hash,
displayName: emp.name,
employeeId: emp.id,
},
});
await tx.userRole.create({ data: { userId: u.id, roleId } });
return u;
};
const admin = await mkUser('admin', adminHash, emps.admin, roles.admin.id);
await mkUser('owner', passwordHash, emps.owner, roles.owner.id);
await mkUser('bizdir', passwordHash, emps.bizdir, roles.biz_director.id);
await mkUser('bizstaff', passwordHash, emps.bizstaff, roles.biz_staff.id);
const dirsoft = await mkUser('dirsoft', passwordHash, emps.dirsoft, roles.tech_director.id);
const dir3d = await mkUser('dir3d', passwordHash, emps.dir3d, roles.tech_director.id);
await mkUser('dirfilm', passwordHash, emps.dirfilm, roles.tech_director.id);
await mkUser('dirmaterial', passwordHash, emps.dirmaterial, roles.tech_director.id);
await mkUser('finance', passwordHash, emps.finance, roles.finance.id);
await mkUser('hr', passwordHash, emps.hr, roles.hr.id);
await mkUser('employee', passwordHash, emps.employee, roles.employee.id);
await mkUser('softpm', passwordHash, softpm, roles.employee.id);
const demoEntity = await tx.legalEntity.create({
data: { name: '演示科技有限公司', shortName: '演示科技', creditNo: '91110000MA0000001X', isDefault: true },
});
const party = await tx.businessParty.create({
data: { name: '某市城投集团', creditNo: '91110000MA9999999X', level: 'A' },
});
await tx.partyContact.create({
data: { partyId: party.id, name: '招标办张工', phone: '13800001111', title: '项目联系人' },
});
await tx.qualification.createMany({
data: [
{
name: '电子与智能化工程专业承包',
code: 'QUAL_INTEL',
keeper: '行政',
expiresAt: new Date(Date.now() + 400 * 86400000),
},
{
name: '涉密信息系统集成乙级',
code: 'SECRET_B',
keeper: '行政',
expiresAt: new Date(Date.now() + 20 * 86400000),
},
{
name: '建筑装修装饰工程专业承包',
code: 'QUAL_DECOR',
keeper: '行政',
expiresAt: new Date(Date.now() - 10 * 86400000),
},
],
});
const dicts: [string, string, string, number][] = [
['projectType', '软件', '软件', 1],
['projectType', '三维', '三维', 2],
['projectType', '摄制', '摄制', 3],
['projectType', '物资', '物资', 4],
['projectType', '集成', '集成', 5],
['tenderMethod', '公开招标', '公开招标', 1],
['tenderMethod', '邀请招标', '邀请招标', 2],
['tenderMethod', '询价', '询价', 3],
['tenderMethod', '竞谈【综合评分】', '竞谈【综合评分】', 4],
['tenderMethod', '竞谈【最低价中】', '竞谈【最低价中】', 5],
['tenderMethod', '单一来源', '单一来源', 6],
['tenderMethod', '其他', '其他', 7],
['qualificationNeed', '无需资质', '无需资质', 1],
['qualificationNeed', '军二', '军二', 2],
['qualificationNeed', '涉乙', '涉乙', 3],
['qualificationNeed', '其他', '其他', 4],
['expenseCategory', '交通', '交通', 1],
['expenseCategory', '餐饮', '餐饮', 2],
['expenseCategory', '住宿', '住宿', 3],
['expenseCategory', '办公', '办公', 4],
['expenseCategory', '招待', '招待', 5],
['expenseCategory', '其他', '其他', 6],
['gender', '男', 'MALE', 1],
['gender', '女', 'FEMALE', 2],
['education', '高中及以下', 'HIGH_SCHOOL', 1],
['education', '专科', 'COLLEGE', 2],
['education', '本科', 'BACHELOR', 3],
['education', '硕士', 'MASTER', 4],
['education', '博士', 'DOCTOR', 5],
];
await tx.dictItem.createMany({
data: dicts.map(([dictType, label, value, sortNo]) => ({ dictType, label, value, sortNo })),
});
await tx.numberRule.createMany({
data: [
{ code: 'BID', name: '投标编号', prefix: 'BID', padLength: 5 },
{ code: 'HT', name: '合同编号', prefix: 'HT', padLength: 5 },
{ code: 'PRJ', name: '项目编号', prefix: 'PRJ', padLength: 5 },
{ code: 'BX', name: '报销编号', prefix: 'BX', padLength: 5 },
{ code: 'JK', name: '借款编号', prefix: 'JK', padLength: 5 },
{ code: 'BZ', name: '保证金编号', prefix: 'BZ', padLength: 5 },
],
});
await tx.hrSetting.create({
data: { id: 'default', tenantId: '1', idMask: true, autoAccount: false, probationMonths: 3, defaultRest: 'SINGLE', workStart: '09:00', workEnd: '18:00' },
});
await tx.attendanceRule.createMany({
data: [
{ name: '单休班次', restSchedule: 'SINGLE', workStart: '09:00', workEnd: '18:00', sortNo: 1, remark: '每周休一天,薪酬按 26.09 天核算' },
{ name: '双休班次', restSchedule: 'DOUBLE', workStart: '09:00', workEnd: '18:00', sortNo: 2, remark: '每周休两天,薪酬按 21.75 天核算' },
],
});
const sealTypes = ['公章', '合同章', '法人章', '法人签名章', '项目负责人章', '其他章'];
await tx.sealItem.createMany({
data: sealTypes.map((sealType) => ({
name: `${demoEntity.shortName}${sealType}`,
sealType,
keeper: '行政',
legalEntityId: demoEntity.id,
status: 'IN_STOCK',
})),
});
const bid = await tx.bidCase.create({
data: {
bidNo: 'BID-2026-00001',
name: '某市展厅数字化升级',
externalNo: 'ZFCG-2026-088',
projectType: '集成',
source: '招标公告',
qualificationNeed: '涉乙',
tenderMethod: '公开招标',
applyMethod: '网上申领',
openMethod: '线下开标',
openPlace: '市公共资源交易中心',
openAt: new Date(Date.now() + 7 * 86400000),
partyId: party.id,
status: 'TECH_INITIAL',
createdById: admin.id,
reviewers: {
create: [
{ userId: dirsoft.id, stage: 'TECH_INITIAL' },
{ userId: dir3d.id, stage: 'TECH_INITIAL' },
],
},
},
});
await tx.bidActionLog.create({
data: {
bidCaseId: bid.id,
actorId: admin.id,
action: 'CREATE',
toStatus: 'TECH_INITIAL',
comment: `发起筛选 ${bid.bidNo}`,
},
});
await tx.approvalTask.createMany({
data: [
{
title: `技术初审会签:${bid.name}`,
bizType: 'BID_TECH_INITIAL',
bizId: bid.id,
assigneeId: dirsoft.id,
},
{
title: `技术初审会签:${bid.name}`,
bizType: 'BID_TECH_INITIAL',
bizId: bid.id,
assigneeId: dir3d.id,
},
],
});
});
}
main()
.then(async () => {
await prisma.$disconnect();
console.log('seed ok');
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});