From 28e0c6de19885be283a42dfe0fe6f7db59ee1e0c Mon Sep 17 00:00:00 2001 From: Script Generator Date: Wed, 1 Jul 2026 16:39:53 +0800 Subject: [PATCH] =?UTF-8?q?chore(data):=20=E6=96=B0=E5=A2=9E=E6=B8=85?= =?UTF-8?q?=E7=A9=BA=E6=95=B0=E6=8D=AE=E5=AF=BC=E5=85=A5=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/prisma/seed-data.spec.ts | 94 ++++++ apps/server/prisma/seed-data.ts | 437 +++++++++++++++++++++++++++ apps/server/prisma/seed.ts | 73 +++++ 3 files changed, 604 insertions(+) create mode 100644 apps/server/prisma/seed-data.spec.ts create mode 100644 apps/server/prisma/seed-data.ts create mode 100644 apps/server/prisma/seed.ts diff --git a/apps/server/prisma/seed-data.spec.ts b/apps/server/prisma/seed-data.spec.ts new file mode 100644 index 0000000..273f178 --- /dev/null +++ b/apps/server/prisma/seed-data.spec.ts @@ -0,0 +1,94 @@ +import { buildAppDataSeed } from './seed-data'; + +function byKey(key: string) { + const entry = buildAppDataSeed().find((item) => item.key === key); + if (!entry) throw new Error(`Missing seed key: ${key}`); + return entry.value as any; +} + +describe('clean app data seed', () => { + it('clears business, workspace, warning, and overtime records', () => { + const emptyArrayKeys = [ + 'version-plans', + 'dev-tasks', + 'test-cases', + 'bugs', + 'task-worklogs', + 'work-activities', + 'xiaobao-risk-insights', + 'xiaobao-risk-snapshots', + 'xiaobao-warning-views', + 'task-categories', + ]; + + for (const key of emptyArrayKeys) { + expect(byKey(key)).toEqual([]); + } + + expect(byKey('requirements').requirements).toEqual([]); + expect(byKey('overtime').records).toEqual([]); + }); + + it('seeds deduped customer source targets split from the customer spreadsheet', () => { + const requirements = byKey('requirements'); + const targets = requirements.sourceTargets; + const names = targets.map((target: { name: string }) => target.name); + + expect(targets).toHaveLength(67); + expect(targets.every((target: { sourceType: string }) => target.sourceType === 'customer')).toBe(true); + expect(new Set(names.map((name: string) => name.trim().toLowerCase())).size).toBe(names.length); + expect(names.some((name: string) => name.includes(',') || name.includes(','))).toBe(false); + expect(names.slice(0, 5)).toEqual(['方砖厂', '全部客户', '晓秧锅', '老船长', '失眠烧烤']); + expect(names).toContain('秦门西安小馆'); + expect(names.at(-1)).toBe('杨记兴'); + }); + + it('creates the default FTB product with deduped projects from the spreadsheet', () => { + const products = byKey('products-overview'); + + expect(products).toHaveLength(1); + expect(products[0]).toEqual( + expect.objectContaining({ + id: 'product-ftb-app', + name: '翻台宝应用', + }), + ); + expect(products[0].projects).toHaveLength(56); + expect(products[0]._count.projects).toBe(56); + expect(products[0].versions).toEqual([]); + + const names = products[0].projects.map((project: { name: string }) => project.name); + expect(new Set(names.map((name: string) => name.trim().toLowerCase())).size).toBe(names.length); + expect(names.slice(0, 5)).toEqual(['通知公告', '产品库', 'AI帮助', '读书', '工作计划']); + expect(names.at(-1)).toBe('增长管理'); + }); + + it('keeps dictionaries and role configuration while only retaining the super admin member', () => { + const requirements = byKey('requirements'); + const overtime = byKey('overtime'); + const members = byKey('members'); + + expect(requirements.types.length).toBeGreaterThan(0); + expect(requirements.platforms.length).toBeGreaterThan(0); + expect(overtime.reasons.length).toBeGreaterThan(0); + + expect(members.members).toEqual([ + expect.objectContaining({ + id: 'm-8', + name: '超级管理员', + roleId: 'role-admin', + isSystem: true, + password: 'Ftb@2024', + }), + ]); + expect(members.roles.map((role: { id: string }) => role.id)).toEqual([ + 'role-admin', + 'role-pm', + 'role-lead', + 'role-dev', + 'role-test', + 'role-design', + ]); + expect(members.passwordRule).toBeTruthy(); + }); +}); diff --git a/apps/server/prisma/seed-data.ts b/apps/server/prisma/seed-data.ts new file mode 100644 index 0000000..0f6e903 --- /dev/null +++ b/apps/server/prisma/seed-data.ts @@ -0,0 +1,437 @@ +export type JsonSeedValue = + | string + | number + | boolean + | null + | JsonSeedValue[] + | { [key: string]: JsonSeedValue }; + +export interface AppDataSeedEntry { + key: string; + value: JsonSeedValue; +} + +export const DEFAULT_PRODUCT_ID = 'product-ftb-app'; +export const DEFAULT_PRODUCT_NAME = '翻台宝应用'; +const seedDate = '2026-07-01'; +const seedCreatedAt = '2026-07-01T00:00:00.000Z'; + +const importedProjectNames = [ + '通知公告', + '产品库', + 'AI帮助', + '读书', + '工作计划', + '考勤', + '埋点统计', + '培训', + '案例学习', + '权限管理', + '人事评价', + '数据分析', + '薪酬', + '巡店', + '中差评', + '值班', + '我的', + '通讯录', + '人事入转调离', + '审批', + 'IM即时聊天', + '全局任务', + '经营报告', + '人才发展', + '电子合同', + '系统管理', + '证件管理', + '问卷调查', + '充值', + '企业微信机器人', + '组织架构', + '日志', + '文化打卡', + '资料库', + '利润管理', + '其它', + '桌访', + 'AI舆情助手', + 'AI员工关系助手', + 'AI经营助手', + '文件列表', + '设备管理', + '录音详情', + '硬件对接', + '词库配置', + '数据同步', + '算法服务', + '硬件管理后台', + '翻台宝后台', + '评分面板', + '桌访整合', + '系统整合', + '绩效', + '日常助手', + '店长助手', + '增长管理', +]; + +export function buildDefaultProjects() { + return importedProjectNames.map((name, index) => ({ + id: `project-ftb-${String(index + 1).padStart(3, '0')}`, + name, + description: '', + createdAt: seedCreatedAt, + })); +} + +const customerSourceTargetNames = [ + '方砖厂', + '全部客户', + '晓秧锅', + '老船长', + '失眠烧烤', + '百年神厨', + '渝是乎', + '王大爷烧烤', + '左庭右院', + '咱妈烀饼', + '叁宝烤肉', + '潼厨', + '苏福记', + '农耕记', + '呼啦面馆', + '古家辣子', + '老郑家', + '管氏', + '六月雪', + '公司内部', + '天山传奇', + '蜀镇', + '灶东家', + '牧家厨房', + '大河宴', + '蓉和妈妈菜', + '记忆包工坊', + '聚龙', + '茅溪', + '八菜一汤', + '醉巴鲜', + '菡湘餐饮', + '洞庭楚湘', + '建国烧烤', + '吾里家', + '蔚蓝书吧', + '新沙烧烤', + '酒煮江湖', + '奎屯俭寨', + '渝厨', + '饼司令', + '重庆面包路', + '101披萨', + '仲和小馆', + '淄博黎叔', + '秦阿哥', + '秦门西安小馆', + '西椒和牛', + '渔痴鱼醉', + '德天肥牛', + '稻香园', + '我叫湘', + '贝勒爷南门涮肉', + '丝恋丝娃娃', + '赣掌门', + '肖记牛肉鱼杂馆', + '朱三伯', + '西安70大院', + '武圣羊汤', + '壶茶馆', + '熊家无二', + '滇芸山', + '壹周餐餐饮', + '钟家凉粉', + '炭亲干锅', + '麻步姐', + '杨记兴', +]; + +function buildCustomerSourceTargets(): JsonSeedValue[] { + return customerSourceTargetNames.map((name, index) => ({ + id: `source-customer-${String(index + 1).padStart(3, '0')}`, + name, + sourceType: 'customer', + createdAt: seedDate, + })); +} + +function buildDefaultProductOverview(): JsonSeedValue { + const projects = buildDefaultProjects(); + return { + id: DEFAULT_PRODUCT_ID, + name: DEFAULT_PRODUCT_NAME, + description: '', + createdAt: seedCreatedAt, + updatedAt: seedCreatedAt, + projects, + versions: [], + _count: { + requirements: 0, + projects: projects.length, + versions: 0, + }, + }; +} + +const requirementTypes: JsonSeedValue[] = [ + { id: 'type-1', name: '新功能', createdAt: '2024-01-01' }, + { id: 'type-2', name: 'BUG修复', createdAt: '2024-01-01' }, + { id: 'type-3', name: '数据需求/埋点需求', createdAt: '2024-01-01' }, + { id: 'type-4', name: '优化需求', createdAt: '2024-01-01' }, + { id: 'type-5', name: '模块重构', createdAt: '2024-01-01' }, + { id: 'type-6', name: '缺陷修复', createdAt: '2024-01-01' }, + { id: 'type-7', name: '功能优化', createdAt: '2024-01-01' }, + { id: 'type-8', name: '插单/紧急需求', createdAt: '2024-01-01' }, +]; + +const requirementPlatforms: JsonSeedValue[] = [ + { id: 'platform-1', name: 'Web', createdAt: '2024-01-01' }, + { id: 'platform-2', name: 'App(iOS)', createdAt: '2024-01-01' }, + { id: 'platform-3', name: 'App(Android)', createdAt: '2024-01-01' }, + { id: 'platform-4', name: '小程序', createdAt: '2024-01-01' }, + { id: 'platform-5', name: 'H5', createdAt: '2024-01-01' }, +]; + +const overtimeReasons: JsonSeedValue[] = [ + { id: 'reason-1', name: '需求变更', createdAt: '2024-01-01' }, + { id: 'reason-2', name: '需求新增', createdAt: '2024-01-01' }, + { id: 'reason-3', name: '版本冲刺', createdAt: '2024-01-01' }, + { id: 'reason-4', name: 'Bug修复', createdAt: '2024-01-01' }, + { id: 'reason-5', name: '线上问题修复', createdAt: '2024-01-01' }, + { id: 'reason-6', name: '测试问题整改', createdAt: '2024-01-01' }, + { id: 'reason-7', name: '技术难点攻关', createdAt: '2024-01-01' }, + { id: 'reason-8', name: '联调问题', createdAt: '2024-01-01' }, + { id: 'reason-9', name: '上游交付延误', createdAt: '2024-01-01' }, + { id: 'reason-10', name: '工作量超负荷', createdAt: '2024-01-01' }, + { id: 'reason-11', name: '返工修改', createdAt: '2024-01-01' }, +]; + +const departments: JsonSeedValue[] = [ + { id: 'dept-1', name: '产品部', order: 1, createdAt: '2024-01-01' }, + { id: 'dept-2', name: '技术部', order: 2, createdAt: '2024-01-01' }, + { id: 'dept-2-1', name: '前端组', order: 1, parentId: 'dept-2', createdAt: '2024-01-01' }, + { id: 'dept-2-2', name: '后端组', order: 2, parentId: 'dept-2', createdAt: '2024-01-01' }, + { id: 'dept-2-3', name: '测试组', order: 3, parentId: 'dept-2', createdAt: '2024-01-01' }, + { id: 'dept-3', name: '设计部', order: 3, createdAt: '2024-01-01' }, + { id: 'dept-4', name: '运营部', order: 4, createdAt: '2024-01-01' }, +]; + +const roles: JsonSeedValue[] = [ + { + id: 'role-admin', + name: '超级管理员', + isSystem: true, + createdAt: '2024-01-01', + description: '拥有系统全部权限', + permissions: ['*'], + }, + { + id: 'role-pm', + name: '产品经理', + createdAt: '2024-01-01', + description: '管理产品和需求', + permissions: [ + 'product:view', + 'product:create', + 'product:edit', + 'product:delete', + 'project:view', + 'project:create', + 'project:edit', + 'project:delete', + 'version:view', + 'version:create', + 'version:edit', + 'version:delete', + 'requirement:view', + 'requirement:create', + 'requirement:edit', + 'requirement:delete', + 'version.req:view', + 'version.req:manage', + 'version.product_plan:view', + 'version.product_plan:manage', + 'overtime:view', + 'member:view', + 'role:view', + 'version.research:view', + 'version.ui_plan:view', + 'version.devtask:view', + 'version.testcase:view', + 'version.bug:view', + 'xiaobao.warning:view', + 'xiaobao.warning:manage', + ], + }, + { + id: 'role-lead', + name: '组长', + createdAt: '2024-01-01', + description: '管理本组任务和本部门加班记录', + permissions: [ + 'product:view', + 'project:view', + 'version:view', + 'requirement:view', + 'version.req:view', + 'version.research:view', + 'version.product_plan:view', + 'version.ui_plan:view', + 'version.devtask:view', + 'version.devtask:manage', + 'version.testcase:view', + 'version.testcase:manage', + 'version.bug:view', + 'version.bug:create', + 'version.bug:edit', + 'version.bug:delete', + 'xiaobao.warning:view', + 'overtime:view', + 'overtime:create', + 'overtime:export', + ], + }, + { + id: 'role-dev', + name: '开发工程师', + createdAt: '2024-01-01', + description: '负责开发任务', + permissions: [ + 'product:view', + 'project:view', + 'version:view', + 'requirement:view', + 'version.req:view', + 'version.devtask:view', + 'version.devtask:manage', + 'version.bug:view', + 'version.bug:edit', + 'version.research:view', + 'version.product_plan:view', + 'version.ui_plan:view', + 'version.testcase:view', + 'overtime:view', + 'overtime:create', + 'xiaobao.warning:view', + ], + }, + { + id: 'role-test', + name: '测试工程师', + createdAt: '2024-01-01', + description: '负责测试任务', + permissions: [ + 'product:view', + 'project:view', + 'version:view', + 'requirement:view', + 'version.req:view', + 'version.testcase:view', + 'version.testcase:manage', + 'version.bug:view', + 'version.bug:create', + 'version.bug:edit', + 'version.bug:delete', + 'version.research:view', + 'version.product_plan:view', + 'version.ui_plan:view', + 'version.devtask:view', + 'overtime:view', + 'overtime:create', + 'xiaobao.warning:view', + ], + }, + { + id: 'role-design', + name: '设计师', + createdAt: '2024-01-01', + description: '负责UI/UX设计', + permissions: [ + 'product:view', + 'project:view', + 'version:view', + 'requirement:view', + 'version.req:view', + 'version.ui_plan:view', + 'version.ui_plan:manage', + 'version.research:view', + 'version.product_plan:view', + 'version.devtask:view', + 'version.testcase:view', + 'version.bug:view', + 'overtime:view', + 'overtime:create', + 'xiaobao.warning:view', + ], + }, +]; + +const superAdminMember: JsonSeedValue = { + id: 'm-8', + name: '超级管理员', + email: 'chenshi@company.com', + phone: '13200132008', + roleId: 'role-admin', + isSystem: true, + password: 'Ftb@2024', + createdAt: '2024-01-01', + departmentId: 'dept-1', +}; + +const passwordRule: JsonSeedValue = { + length: 8, + prefix: 'Ftb', + includeNumbers: true, + includeSpecial: false, + includeLowercase: true, + includeUppercase: true, +}; + +export function buildAppDataSeed(): AppDataSeedEntry[] { + return [ + { key: 'products-overview', value: [buildDefaultProductOverview()] }, + { + key: 'requirements', + value: { + requirements: [], + sourceTargets: buildCustomerSourceTargets(), + types: requirementTypes, + platforms: requirementPlatforms, + }, + }, + { key: 'version-plans', value: [] }, + { key: 'dev-tasks', value: [] }, + { key: 'test-cases', value: [] }, + { key: 'bugs', value: [] }, + { + key: 'members', + value: { + departments, + members: [superAdminMember], + roles, + passwordRule, + }, + }, + { key: 'task-categories', value: [] }, + { key: 'task-worklogs', value: [] }, + { key: 'work-activities', value: [] }, + { key: 'xiaobao-risk-insights', value: [] }, + { key: 'xiaobao-risk-snapshots', value: [] }, + { key: 'xiaobao-warning-views', value: [] }, + { + key: 'overtime', + value: { + records: [], + reasons: overtimeReasons, + }, + }, + ]; +} diff --git a/apps/server/prisma/seed.ts b/apps/server/prisma/seed.ts new file mode 100644 index 0000000..fef430a --- /dev/null +++ b/apps/server/prisma/seed.ts @@ -0,0 +1,73 @@ +import { Prisma, PrismaClient } from '@prisma/client'; +import { + DEFAULT_PRODUCT_ID, + DEFAULT_PRODUCT_NAME, + buildAppDataSeed, + buildDefaultProjects, +} from './seed-data'; + +const prisma = new PrismaClient(); + +async function clearRelationalBusinessData() { + await prisma.$transaction([ + prisma.comment.deleteMany(), + prisma.taskWatcher.deleteMany(), + prisma.task.deleteMany(), + prisma.sprint.deleteMany(), + prisma.projectMember.deleteMany(), + prisma.requirement.deleteMany(), + prisma.version.deleteMany(), + prisma.project.deleteMany(), + prisma.product.deleteMany(), + ]); +} + +async function seedAppData() { + const entries = buildAppDataSeed(); + + for (const entry of entries) { + const value = entry.value as Prisma.InputJsonValue; + await prisma.appData.upsert({ + where: { key: entry.key }, + update: { value }, + create: { key: entry.key, value }, + }); + } + + return entries.length; +} + +async function seedRelationalBusinessData() { + await prisma.product.create({ + data: { + id: DEFAULT_PRODUCT_ID, + name: DEFAULT_PRODUCT_NAME, + description: '', + }, + }); + + await prisma.project.createMany({ + data: buildDefaultProjects().map((project) => ({ + id: project.id, + productId: DEFAULT_PRODUCT_ID, + name: project.name, + description: project.description, + })), + }); +} + +async function main() { + await clearRelationalBusinessData(); + await seedRelationalBusinessData(); + const count = await seedAppData(); + console.log(`Seeded ${count} app_data keys with clean branch data.`); +} + +main() + .catch((error) => { + console.error(error); + process.exitCode = 1; + }) + .finally(async () => { + await prisma.$disconnect(); + });