require("dotenv/config"); const { createPrismaClient } = require("../prismaClient"); const { hashPassword } = require("../utils/crypto"); async function ensureDepartment(prisma, payload) { const now = new Date(); const existing = await prisma.department.findFirst({ where: { name: payload.name, parent_id: payload.parent_id ?? null, }, }); if (existing) { return prisma.department.update({ where: { id: existing.id }, data: { parent_id: payload.parent_id ?? null, archived_at: null, updated_at: now, }, }); } return prisma.department.create({ data: { name: payload.name, parent_id: payload.parent_id ?? null, created_at: now, updated_at: now, }, }); } async function ensureLegalEntity(prisma, payload) { const now = new Date(); const existing = await prisma.legalEntity.findUnique({ where: { code: payload.code }, select: { id: true }, }); if (existing) { return prisma.legalEntity.update({ where: { id: existing.id }, data: { name: payload.name, short_name: payload.shortName ?? null, currency_code: payload.currencyCode, tax_no: payload.taxNo ?? null, enabled: true, archived_at: null, updated_at: now, }, }); } return prisma.legalEntity.create({ data: { code: payload.code, name: payload.name, short_name: payload.shortName ?? null, currency_code: payload.currencyCode, tax_no: payload.taxNo ?? null, enabled: true, archived_at: null, created_at: now, updated_at: now, }, }); } async function ensureCostCenter(prisma, payload) { const now = new Date(); const existing = await prisma.costCenter.findFirst({ where: { legal_entity_id: payload.legalEntityId, code: payload.code, }, select: { id: true }, }); if (existing) { return prisma.costCenter.update({ where: { id: existing.id }, data: { name: payload.name, parent_id: payload.parentId ?? null, owner_employee_id: payload.ownerEmployeeId ?? null, enabled: true, archived_at: null, updated_at: now, }, }); } return prisma.costCenter.create({ data: { legal_entity_id: payload.legalEntityId, code: payload.code, name: payload.name, parent_id: payload.parentId ?? null, owner_employee_id: payload.ownerEmployeeId ?? null, enabled: true, archived_at: null, created_at: now, updated_at: now, }, }); } async function ensureBudgetPool(prisma, payload) { const now = new Date(); const existing = await prisma.budgetPool.findUnique({ where: { code: payload.code }, select: { id: true }, }); const data = { name: payload.name, department_id: payload.departmentId ?? null, legal_entity_id: payload.legalEntityId ?? null, cost_center_id: payload.costCenterId ?? null, status: "active", period_start: payload.periodStart, period_end: payload.periodEnd, allocated_amount: payload.allocatedAmount, warning_threshold_percent: payload.warningThresholdPercent ?? 80, archived_at: null, updated_at: now, updated_by: null, }; if (existing) { return prisma.budgetPool.update({ where: { id: existing.id }, data, }); } return prisma.budgetPool.create({ data: { code: payload.code, used_amount: payload.usedAmount ?? "0.00", created_at: now, created_by: null, ...data, }, }); } async function ensureSupplier(prisma, payload) { const now = new Date(); const existing = await prisma.supplier.findUnique({ where: { code: payload.code }, select: { id: true }, }); if (existing) { return prisma.supplier.update({ where: { id: existing.id }, data: { name: payload.name, short_name: payload.shortName ?? null, tax_no: payload.taxNo ?? null, contact_name: payload.contactName ?? null, contact_phone: payload.contactPhone ?? null, payment_terms: payload.paymentTerms ?? null, legal_entity_id: payload.legalEntityId ?? null, enabled: true, archived_at: null, updated_at: now, }, }); } return prisma.supplier.create({ data: { code: payload.code, name: payload.name, short_name: payload.shortName ?? null, tax_no: payload.taxNo ?? null, contact_name: payload.contactName ?? null, contact_phone: payload.contactPhone ?? null, payment_terms: payload.paymentTerms ?? null, legal_entity_id: payload.legalEntityId ?? null, enabled: true, archived_at: null, created_at: now, updated_at: now, }, }); } async function ensureHolidayCalendar(prisma, payload) { const now = new Date(); const existing = await prisma.holidayCalendar.findUnique({ where: { code: payload.code }, select: { id: true }, }); const calendar = existing ? await prisma.holidayCalendar.update({ where: { id: existing.id }, data: { name: payload.name, timezone: payload.timezone ?? "Asia/Shanghai", legal_entity_id: payload.legalEntityId ?? null, enabled: true, archived_at: null, updated_at: now, }, }) : await prisma.holidayCalendar.create({ data: { code: payload.code, name: payload.name, timezone: payload.timezone ?? "Asia/Shanghai", legal_entity_id: payload.legalEntityId ?? null, enabled: true, archived_at: null, created_at: now, updated_at: now, }, }); await prisma.holidayCalendarDay.deleteMany({ where: { calendar_id: calendar.id }, }); if (payload.days?.length) { await prisma.holidayCalendarDay.createMany({ data: payload.days.map((item) => ({ calendar_id: calendar.id, date: new Date(`${item.date}T00:00:00.000Z`), day_type: item.dayType, name: item.name, note: item.note ?? null, created_at: now, created_by: null, updated_at: now, updated_by: null, })), }); } if (payload.setAsDefault && payload.legalEntityId) { await prisma.legalEntity.update({ where: { id: payload.legalEntityId }, data: { default_holiday_calendar_id: calendar.id, updated_at: now, }, }); } return calendar; } async function ensureFeatureFlag(prisma, payload) { const now = new Date(); const existing = await prisma.featureFlag.findUnique({ where: { flag_key: payload.flag_key }, select: { id: true, version: true }, }); if (existing) { return prisma.featureFlag.update({ where: { id: existing.id }, data: { override_mode: payload.override_mode, rollout_type: payload.rollout_type, department_ids: payload.department_ids, note: payload.note, version: existing.version + 1, updated_at: now, }, }); } return prisma.featureFlag.create({ data: { ...payload, created_at: now, updated_at: now, }, }); } async function ensureWorkspaceSetting(prisma) { const current = await prisma.workspaceSetting.findFirst({ select: { id: true } }); const data = { profile: "small_team_internal", org_mode: "single_company", primary_entry_mode: "password", notification_strategy: "in_app", device_priority: "desktop", module_visibility_json: { portal: "main", team: "main", leaveRequests: "main", reimbursements: "main", assets: "main", weeklyReports: "main", todos: "main", announcements: "main", workspace: "admin", people: "admin", workflowDefinitions: "admin", systemSettings: "admin", featureFlags: "admin", auditLogs: "admin", purchaseRequests: "admin", travelRequests: "admin", budgets: "admin", paymentBatches: "admin", apOperations: "admin", cutover: "admin", erpIntegrations: "admin", }, }; if (current) { return prisma.workspaceSetting.update({ where: { id: current.id }, data, }); } return prisma.workspaceSetting.create({ data }); } async function ensureEmployee(prisma, payload) { const now = new Date(); const passwordHash = await hashPassword(payload.password); const existing = await prisma.employee.findFirst({ where: { email: payload.email }, select: { id: true }, }); if (existing) { const employee = await prisma.employee.update({ where: { id: existing.id }, data: { employee_no: payload.employeeNo, name: payload.name, phone: payload.phone, department_id: payload.departmentId, legal_entity_id: payload.legalEntityId ?? null, title: payload.title, direct_manager_id: payload.directManagerId, hire_date: payload.hireDate, status: "active", bank_account_encrypted: "ENC_DEV_ONLY", bank_name: "DEV_BANK", password_hash: passwordHash, must_change_password: false, updated_at: now, }, select: { id: true, email: true }, }); await prisma.employeeRole.deleteMany({ where: { employee_id: employee.id }, }); await prisma.employeeRole.createMany({ data: payload.roles.map((roleCode) => ({ employee_id: employee.id, role_code: roleCode, created_at: now, })), skipDuplicates: true, }); return employee; } const employee = await prisma.employee.create({ data: { employee_no: payload.employeeNo, name: payload.name, phone: payload.phone, email: payload.email, department_id: payload.departmentId, legal_entity_id: payload.legalEntityId ?? null, title: payload.title, direct_manager_id: payload.directManagerId, hire_date: payload.hireDate, status: "active", bank_account_encrypted: "ENC_DEV_ONLY", bank_name: "DEV_BANK", password_hash: passwordHash, must_change_password: false, created_at: now, updated_at: now, }, select: { id: true, email: true }, }); await prisma.employeeRole.createMany({ data: payload.roles.map((roleCode) => ({ employee_id: employee.id, role_code: roleCode, created_at: now, })), skipDuplicates: true, }); return employee; } async function main() { const { prisma, pool } = createPrismaClient(); await prisma.$connect(); const hireDate = new Date("2020-01-01"); const currentYear = new Date().getUTCFullYear(); const fiscalPeriodStart = new Date(`${currentYear}-01-01T00:00:00.000Z`); const fiscalPeriodEnd = new Date(`${currentYear}-12-31T00:00:00.000Z`); try { const rootDepartment = await ensureDepartment(prisma, { name: "公司" }); const managementDepartment = await ensureDepartment(prisma, { name: "管理部", parent_id: rootDepartment.id, }); const financeDepartment = await ensureDepartment(prisma, { name: "财务部", parent_id: rootDepartment.id, }); const hrDepartment = await ensureDepartment(prisma, { name: "人事部", parent_id: rootDepartment.id, }); const administrationDepartment = await ensureDepartment(prisma, { name: "行政部", parent_id: rootDepartment.id, }); const rdDepartment = await ensureDepartment(prisma, { name: "研发部", parent_id: rootDepartment.id, }); const legalEntity = await ensureLegalEntity(prisma, { code: "HYB_CN", name: "海音达科技有限公司", shortName: "海音达", currencyCode: "CNY", taxNo: null, }); await ensureWorkspaceSetting(prisma); for (const flagKey of [ "portal_home_v1", "announcements_v1", "global_search_v1", "presence_board_v1", "attendance_v1", "attendance_unified_v1", "weekly_report_v1", "employee_lifecycle_v1", "todo_board_v1", "project_domain_v1", "project_linking_v1", "project_communication_log_v1", "project_closure_checklist_v1", "confidential_doc_refs_v1", ]) { await ensureFeatureFlag(prisma, { flag_key: flagKey, override_mode: "force_on", rollout_type: "global", department_ids: [], note: "dev default: force_on", }); } const ceo = await ensureEmployee(prisma, { employeeNo: process.env.SEED_CEO_EMPLOYEE_NO ?? "E0001", name: "CEO", phone: process.env.SEED_CEO_PHONE ?? "13900000000", email: process.env.SEED_CEO_EMAIL ?? "ceo@hyinda.com", password: process.env.SEED_CEO_PASSWORD ?? "CeoPassword1234", departmentId: rootDepartment.id, legalEntityId: legalEntity.id, title: "总经理", directManagerId: null, roles: ["ceo", "employee"], hireDate, }); const admin = await ensureEmployee(prisma, { employeeNo: process.env.SEED_ADMIN_EMPLOYEE_NO ?? "E0000", name: "Admin", phone: process.env.SEED_ADMIN_PHONE ?? "13800000000", email: process.env.SEED_ADMIN_EMAIL ?? "admin@hyinda.com", password: process.env.SEED_ADMIN_PASSWORD ?? "AdminPassword1234", departmentId: managementDepartment.id, legalEntityId: legalEntity.id, title: "系统管理员", directManagerId: ceo.id, roles: ["admin", "employee"], hireDate, }); const manager = await ensureEmployee(prisma, { employeeNo: process.env.SEED_MANAGER_EMPLOYEE_NO ?? "E0100", name: "Manager", phone: process.env.SEED_MANAGER_PHONE ?? "13700000000", email: process.env.SEED_MANAGER_EMAIL ?? "manager@hyinda.com", password: process.env.SEED_MANAGER_PASSWORD ?? "ManagerPassword1234", departmentId: rdDepartment.id, legalEntityId: legalEntity.id, title: "研发经理", directManagerId: ceo.id, roles: ["manager", "employee"], hireDate, }); const employee = await ensureEmployee(prisma, { employeeNo: process.env.SEED_EMPLOYEE_EMPLOYEE_NO ?? "E0101", name: "Employee", phone: process.env.SEED_EMPLOYEE_PHONE ?? "13600000000", email: process.env.SEED_EMPLOYEE_EMAIL ?? "employee@hyinda.com", password: process.env.SEED_EMPLOYEE_PASSWORD ?? "EmployeePassword1234", departmentId: rdDepartment.id, legalEntityId: legalEntity.id, title: "研发工程师", directManagerId: manager.id, roles: ["employee"], hireDate, }); const finance = await ensureEmployee(prisma, { employeeNo: process.env.SEED_FINANCE_EMPLOYEE_NO ?? "E0200", name: "Finance", phone: process.env.SEED_FINANCE_PHONE ?? "13500000000", email: process.env.SEED_FINANCE_EMAIL ?? "finance@hyinda.com", password: process.env.SEED_FINANCE_PASSWORD ?? "FinancePassword1234", departmentId: financeDepartment.id, legalEntityId: legalEntity.id, title: "财务专员", directManagerId: ceo.id, roles: ["finance", "employee"], hireDate, }); const financeMgr = await ensureEmployee(prisma, { employeeNo: process.env.SEED_FINANCE_MGR_EMPLOYEE_NO ?? "E0201", name: "Finance Manager", phone: process.env.SEED_FINANCE_MGR_PHONE ?? "13400000000", email: process.env.SEED_FINANCE_MGR_EMAIL ?? "finance_mgr@hyinda.com", password: process.env.SEED_FINANCE_MGR_PASSWORD ?? "FinanceMgrPassword1234", departmentId: financeDepartment.id, legalEntityId: legalEntity.id, title: "财务主管", directManagerId: ceo.id, roles: ["finance", "finance_mgr", "employee"], hireDate, }); const hr = await ensureEmployee(prisma, { employeeNo: process.env.SEED_HR_EMPLOYEE_NO ?? "E0300", name: "HR", phone: process.env.SEED_HR_PHONE ?? "13300000000", email: process.env.SEED_HR_EMAIL ?? "hr@hyinda.com", password: process.env.SEED_HR_PASSWORD ?? "HrPassword1234", departmentId: hrDepartment.id, legalEntityId: legalEntity.id, title: "人事专员", directManagerId: ceo.id, roles: ["hr"], hireDate, }); const administration = await ensureEmployee(prisma, { employeeNo: process.env.SEED_ADMINISTRATION_EMPLOYEE_NO ?? "E0400", name: "Administration", phone: process.env.SEED_ADMINISTRATION_PHONE ?? "13200000000", email: process.env.SEED_ADMINISTRATION_EMAIL ?? "administration@hyinda.com", password: process.env.SEED_ADMINISTRATION_PASSWORD ?? "AdministrationPassword1234", departmentId: administrationDepartment.id, legalEntityId: legalEntity.id, title: "行政专员", directManagerId: manager.id, roles: ["administration"], hireDate, }); const holidayCalendar = await ensureHolidayCalendar(prisma, { code: "HYB_CN_STD", name: "海音达默认日历", timezone: "Asia/Shanghai", legalEntityId: legalEntity.id, setAsDefault: true, days: [ { date: "2026-05-04", dayType: "holiday", name: "公司调休假" }, { date: "2026-05-05", dayType: "holiday", name: "公司调休假" }, { date: "2026-05-09", dayType: "adjusted_workday", name: "周末补班" }, ], }); const managementCostCenter = await ensureCostCenter(prisma, { legalEntityId: legalEntity.id, code: "MGMT", name: "管理中心", ownerEmployeeId: admin.id, }); await ensureCostCenter(prisma, { legalEntityId: legalEntity.id, code: "FIN", name: "财务中心", ownerEmployeeId: financeMgr.id, }); await ensureCostCenter(prisma, { legalEntityId: legalEntity.id, code: "HR", name: "人力中心", ownerEmployeeId: hr.id, }); const rdCostCenter = await ensureCostCenter(prisma, { legalEntityId: legalEntity.id, code: "RD", name: "研发中心", ownerEmployeeId: manager.id, }); const supplier = await ensureSupplier(prisma, { code: "HYB_DEMO_VENDOR", name: "海音达演示供应商", shortName: "演示供应商", taxNo: "91440300DEMO12345X", contactName: "张采购", contactPhone: "0755-12345678", paymentTerms: "月结30天", legalEntityId: legalEntity.id, }); const managementBudgetPool = await ensureBudgetPool(prisma, { code: `DEV_BUDGET_MGMT_${currentYear}`, name: `${currentYear} 管理中心预算池`, legalEntityId: legalEntity.id, costCenterId: managementCostCenter.id, periodStart: fiscalPeriodStart, periodEnd: fiscalPeriodEnd, allocatedAmount: "500000.00", warningThresholdPercent: 80, }); const rdBudgetPool = await ensureBudgetPool(prisma, { code: `DEV_BUDGET_RD_${currentYear}`, name: `${currentYear} 研发部预算池`, departmentId: rdDepartment.id, periodStart: fiscalPeriodStart, periodEnd: fiscalPeriodEnd, allocatedAmount: "800000.00", warningThresholdPercent: 75, }); // eslint-disable-next-line no-console console.log("Seeded dev users:"); // eslint-disable-next-line no-console console.log(`- CEO: ${ceo.email} / ${process.env.SEED_CEO_PASSWORD ?? "CeoPassword1234"}`); // eslint-disable-next-line no-console console.log(`- Admin: ${admin.email} / ${process.env.SEED_ADMIN_PASSWORD ?? "AdminPassword1234"}`); // eslint-disable-next-line no-console console.log(`- Manager: ${manager.email} / ${process.env.SEED_MANAGER_PASSWORD ?? "ManagerPassword1234"}`); // eslint-disable-next-line no-console console.log(`- Employee: ${employee.email} / ${process.env.SEED_EMPLOYEE_PASSWORD ?? "EmployeePassword1234"}`); // eslint-disable-next-line no-console console.log(`- Finance: ${finance.email} / ${process.env.SEED_FINANCE_PASSWORD ?? "FinancePassword1234"}`); // eslint-disable-next-line no-console console.log(`- Finance Manager: ${financeMgr.email} / ${process.env.SEED_FINANCE_MGR_PASSWORD ?? "FinanceMgrPassword1234"}`); // eslint-disable-next-line no-console console.log(`- HR: ${hr.email} / ${process.env.SEED_HR_PASSWORD ?? "HrPassword1234"}`); // eslint-disable-next-line no-console console.log(`- Administration: ${administration.email} / ${process.env.SEED_ADMINISTRATION_PASSWORD ?? "AdministrationPassword1234"}`); // eslint-disable-next-line no-console console.log(`- Legal Entity: ${legalEntity.code} / ${legalEntity.name}`); // eslint-disable-next-line no-console console.log(`- HolidayCalendar: ${holidayCalendar.code} / ${holidayCalendar.name}`); // eslint-disable-next-line no-console console.log(`- Supplier: ${supplier.code} / ${supplier.name}`); // eslint-disable-next-line no-console console.log(`- Budget Pools: ${managementBudgetPool.code}, ${rdBudgetPool.code}`); } finally { await prisma.$disconnect(); await pool.end(); } } main().catch((error) => { // eslint-disable-next-line no-console console.error(error); process.exit(1); });