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 = {}; 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); });