import type { INestApplication } from '@nestjs/common'; import { NotFoundException } from '@nestjs/common'; import request from 'supertest'; import { Role } from '../../../src/generated/prisma/enums.js'; import { PrismaService } from '../../../src/prisma.service.js'; import { E2E_SEED_CREDENTIALS, E2E_SEED_PASSWORD, } from '../fixtures/e2e-roles.js'; import { expectSuccessEnvelope } from './e2e-http.helper.js'; export interface E2ESeedFixtures { hospitalAId: number; hospitalBId: number; departmentA1Id: number; departmentA2Id: number; departmentB1Id: number; groupA1Id: number; groupA2Id: number; groupB1Id: number; catalogs: { adjustableValveId: number; highPressureValveId: number; }; users: { systemAdminId: number; hospitalAdminAId: number; directorAId: number; leaderAId: number; doctorAId: number; doctorA2Id: number; doctorA3Id: number; doctorBId: number; engineerAId: number; engineerBId: number; }; patients: { patientA1Id: number; patientA2Id: number; patientA3Id: number; patientB1Id: number; }; devices: { deviceA1Id: number; deviceA2Id: number; deviceA3Id: number; deviceA4InactiveId: number; deviceB1Id: number; }; } interface SeedUserScope { id: number; hospitalId: number | null; departmentId: number | null; groupId: number | null; } const OPEN_IDS = { systemAdmin: 'seed-system-admin-openid', hospitalAdminA: 'seed-hospital-admin-a-openid', hospitalAdminB: 'seed-hospital-admin-b-openid', directorA: 'seed-director-a-openid', leaderA: 'seed-leader-a-openid', doctorA: 'seed-doctor-a-openid', doctorA2: 'seed-doctor-a2-openid', doctorA3: 'seed-doctor-a3-openid', doctorB: 'seed-doctor-b-openid', engineerA: 'seed-engineer-a-openid', engineerB: 'seed-engineer-b-openid', } as const; const FIXTURE_NAMES = { hospitalA: 'Seed Hospital A', hospitalB: 'Seed Hospital B', departmentA1: 'Neurosurgery-A1', departmentA2: 'Cardiology-A2', departmentB1: 'Neurosurgery-B1', groupA1: 'Shift-A1', groupA2: 'Shift-A2', groupB1: 'Shift-B1', adjustableCatalog: 'SEED-ADJUSTABLE-VALVE', highPressureCatalog: 'SEED-HIGH-PRESSURE-VALVE', } as const; const EXTRA_PHONES = { hospitalAdminB: '13800001101', doctorA2: '13800001204', doctorA3: '13800001304', doctorB: '13800001104', engineerB: '13800001105', } as const; const SHARED_PATIENT_IDENTITY = { phone: '13800002001', idCard: '110101199001010011', } as const; export async function ensureE2EFixtures( app: INestApplication, prisma: PrismaService, ): Promise { const existingSystemAdmin = await prisma.user.findFirst({ where: { phone: E2E_SEED_CREDENTIALS[Role.SYSTEM_ADMIN].phone, role: Role.SYSTEM_ADMIN, }, select: { id: true }, }); if (!existingSystemAdmin) { await bootstrapFixturesViaApi(app); } try { return await loadSeedFixtures(prisma); } catch (error) { await repairFixturesViaApi(app, prisma); return loadSeedFixtures(prisma); } } async function bootstrapFixturesViaApi(app: INestApplication) { const server = app.getHttpServer(); await createSystemAdmin(server); const systemAdminToken = await loginByCredential(server, { phone: E2E_SEED_CREDENTIALS[Role.SYSTEM_ADMIN].phone, password: E2E_SEED_PASSWORD, role: Role.SYSTEM_ADMIN, }); const hospitalA = await createWithToken( server, systemAdminToken, '/b/organization/hospitals', { name: FIXTURE_NAMES.hospitalA }, ); const hospitalB = await createWithToken( server, systemAdminToken, '/b/organization/hospitals', { name: FIXTURE_NAMES.hospitalB }, ); const hospitalAdminA = await createWithToken( server, systemAdminToken, '/users', { name: 'Seed Hospital Admin A', phone: E2E_SEED_CREDENTIALS[Role.HOSPITAL_ADMIN].phone, password: E2E_SEED_PASSWORD, role: Role.HOSPITAL_ADMIN, hospitalId: hospitalA.id, openId: OPEN_IDS.hospitalAdminA, }, ); await createWithToken(server, systemAdminToken, '/users', { name: 'Seed Hospital Admin B', phone: EXTRA_PHONES.hospitalAdminB, password: E2E_SEED_PASSWORD, role: Role.HOSPITAL_ADMIN, hospitalId: hospitalB.id, openId: OPEN_IDS.hospitalAdminB, }); const engineerA = await createWithToken(server, systemAdminToken, '/users', { name: 'Seed Engineer A', phone: E2E_SEED_CREDENTIALS[Role.ENGINEER].phone, password: E2E_SEED_PASSWORD, role: Role.ENGINEER, hospitalId: hospitalA.id, openId: OPEN_IDS.engineerA, }); const engineerB = await createWithToken(server, systemAdminToken, '/users', { name: 'Seed Engineer B', phone: EXTRA_PHONES.engineerB, password: E2E_SEED_PASSWORD, role: Role.ENGINEER, hospitalId: hospitalB.id, openId: OPEN_IDS.engineerB, }); await patchWithToken( server, systemAdminToken, `/b/users/${engineerA.id}/assign-engineer-hospital`, { hospitalId: hospitalA.id }, ); await patchWithToken( server, systemAdminToken, `/b/users/${engineerB.id}/assign-engineer-hospital`, { hospitalId: hospitalB.id }, ); const hospitalAdminAToken = await loginByCredential(server, { phone: hospitalAdminA.phone, password: E2E_SEED_PASSWORD, role: Role.HOSPITAL_ADMIN, hospitalId: hospitalA.id, }); const hospitalAdminBToken = await loginByCredential(server, { phone: EXTRA_PHONES.hospitalAdminB, password: E2E_SEED_PASSWORD, role: Role.HOSPITAL_ADMIN, hospitalId: hospitalB.id, }); const departmentA1 = await createWithToken( server, hospitalAdminAToken, '/b/organization/departments', { name: FIXTURE_NAMES.departmentA1, hospitalId: hospitalA.id, }, ); const departmentA2 = await createWithToken( server, hospitalAdminAToken, '/b/organization/departments', { name: FIXTURE_NAMES.departmentA2, hospitalId: hospitalA.id, }, ); const departmentB1 = await createWithToken( server, hospitalAdminBToken, '/b/organization/departments', { name: FIXTURE_NAMES.departmentB1, hospitalId: hospitalB.id, }, ); const groupA1 = await createWithToken( server, hospitalAdminAToken, '/b/organization/groups', { name: FIXTURE_NAMES.groupA1, departmentId: departmentA1.id, }, ); const groupA2 = await createWithToken( server, hospitalAdminAToken, '/b/organization/groups', { name: FIXTURE_NAMES.groupA2, departmentId: departmentA2.id, }, ); const groupB1 = await createWithToken( server, hospitalAdminBToken, '/b/organization/groups', { name: FIXTURE_NAMES.groupB1, departmentId: departmentB1.id, }, ); const directorA = await createWithToken( server, hospitalAdminAToken, '/users', { name: 'Seed Director A', phone: E2E_SEED_CREDENTIALS[Role.DIRECTOR].phone, password: E2E_SEED_PASSWORD, role: Role.DIRECTOR, hospitalId: hospitalA.id, departmentId: departmentA1.id, openId: OPEN_IDS.directorA, }, ); await createWithToken(server, hospitalAdminAToken, '/users', { name: 'Seed Leader A', phone: E2E_SEED_CREDENTIALS[Role.LEADER].phone, password: E2E_SEED_PASSWORD, role: Role.LEADER, hospitalId: hospitalA.id, departmentId: departmentA1.id, groupId: groupA1.id, openId: OPEN_IDS.leaderA, }); const doctorA = await createWithToken(server, hospitalAdminAToken, '/users', { name: 'Seed Doctor A', phone: E2E_SEED_CREDENTIALS[Role.DOCTOR].phone, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: hospitalA.id, departmentId: departmentA1.id, groupId: groupA1.id, openId: OPEN_IDS.doctorA, }); const doctorA2 = await createWithToken( server, hospitalAdminAToken, '/users', { name: 'Seed Doctor A2', phone: EXTRA_PHONES.doctorA2, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: hospitalA.id, departmentId: departmentA1.id, groupId: groupA1.id, openId: OPEN_IDS.doctorA2, }, ); const doctorA3 = await createWithToken( server, hospitalAdminAToken, '/users', { name: 'Seed Doctor A3', phone: EXTRA_PHONES.doctorA3, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: hospitalA.id, departmentId: departmentA2.id, groupId: groupA2.id, openId: OPEN_IDS.doctorA3, }, ); const doctorB = await createWithToken(server, hospitalAdminBToken, '/users', { name: 'Seed Doctor B', phone: EXTRA_PHONES.doctorB, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: hospitalB.id, departmentId: departmentB1.id, groupId: groupB1.id, openId: OPEN_IDS.doctorB, }); await bootstrapDictionaries(server, systemAdminToken); const adjustableCatalog = await createWithToken( server, systemAdminToken, '/b/devices/catalogs', { modelCode: FIXTURE_NAMES.adjustableCatalog, manufacturer: 'Seed MedTech', name: 'Seed 可调压分流阀', isValve: true, pressureLevels: ['0.5', '1', '1.5'], notes: 'Seed 全局可调压目录样例', }, ); await createWithToken(server, systemAdminToken, '/b/devices/catalogs', { modelCode: FIXTURE_NAMES.highPressureCatalog, manufacturer: 'Seed MedTech', name: 'Seed 高压挡位阀', isValve: true, pressureLevels: ['10', '20', '30'], notes: 'Seed 高压挡位目录样例', }); const doctorAToken = await loginByCredential(server, { phone: doctorA.phone, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: hospitalA.id, }); const doctorA2Token = await loginByCredential(server, { phone: EXTRA_PHONES.doctorA2, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: hospitalA.id, }); const doctorA3Token = await loginByCredential(server, { phone: EXTRA_PHONES.doctorA3, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: hospitalA.id, }); const doctorBToken = await loginByCredential(server, { phone: EXTRA_PHONES.doctorB, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: hospitalB.id, }); const engineerAToken = await loginByCredential(server, { phone: E2E_SEED_CREDENTIALS[Role.ENGINEER].phone, password: E2E_SEED_PASSWORD, role: Role.ENGINEER, hospitalId: hospitalA.id, }); const patientA1 = await createWithToken(server, doctorAToken, '/b/patients', { name: 'Seed Patient A1', inpatientNo: 'ZYH-A-0001', projectName: '脑积水随访项目-A', phone: SHARED_PATIENT_IDENTITY.phone, idCard: SHARED_PATIENT_IDENTITY.idCard, doctorId: doctorA.id, initialSurgery: { surgeryDate: '2024-06-01T08:00:00.000Z', surgeryName: '首次脑室腹腔分流术', preOpPressure: 24, primaryDisease: '先天性脑积水', hydrocephalusTypes: ['交通性'], notes: '首台手术', devices: [ { implantCatalogId: adjustableCatalog.id, shuntMode: 'VPS', proximalPunctureAreas: ['额角'], valvePlacementSites: ['耳后'], distalShuntDirection: '腹腔', initialPressure: '1', implantNotes: 'Seed A1 弃用历史设备', labelImageUrl: 'https://seed.example.com/labels/a1-004.jpg', }, ], }, }); const oldDeviceId = patientA1.devices?.[0]?.id; if (!oldDeviceId) { throw new Error('failed to create seed old device'); } const revisionA1 = await createWithToken( server, doctorAToken, `/b/patients/${patientA1.id}/surgeries`, { surgeryDate: '2025-09-10T08:00:00.000Z', surgeryName: '分流系统翻修术', preOpPressure: 18, primaryDisease: '分流功能障碍', hydrocephalusTypes: ['交通性', '高压性'], previousShuntSurgeryDate: '2024-06-01T08:00:00.000Z', preOpMaterials: [ { type: 'IMAGE', url: 'https://seed.example.com/a1-ct-preop.png', name: 'Seed A1 术前 CT', }, ], notes: '二次手术,保留原设备历史', abandonedDeviceIds: [oldDeviceId], devices: [ { implantCatalogId: adjustableCatalog.id, shuntMode: 'VPS', proximalPunctureAreas: ['额角'], valvePlacementSites: ['耳后'], distalShuntDirection: '腹腔', initialPressure: '1', implantNotes: 'Seed A1 当前在用设备', labelImageUrl: 'https://seed.example.com/labels/a1-001.jpg', }, ], }, ); const patientA2 = await createWithToken( server, doctorA2Token, '/b/patients', { name: 'Seed Patient A2', inpatientNo: 'ZYH-A-0002', projectName: '脑积水随访项目-A', phone: '13800002002', idCard: '110101199002020022', doctorId: doctorA2.id, initialSurgery: { surgeryDate: '2025-12-15T08:00:00.000Z', surgeryName: '脑室腹腔分流术', preOpPressure: 20, primaryDisease: '肿瘤相关脑积水', hydrocephalusTypes: ['梗阻性'], devices: [ { implantCatalogId: adjustableCatalog.id, shuntMode: 'VPS', proximalPunctureAreas: ['枕角'], valvePlacementSites: ['胸前'], distalShuntDirection: '腹腔', initialPressure: '1', implantNotes: 'Seed A2 当前在用设备', labelImageUrl: 'https://seed.example.com/labels/a2-002.jpg', }, ], }, }, ); await createWithToken(server, doctorA3Token, '/b/patients', { name: 'Seed Patient A3', inpatientNo: 'ZYH-A-0003', projectName: '脑积水随访项目-A', phone: '13800002003', idCard: '110101199003030033', doctorId: doctorA3.id, initialSurgery: { surgeryDate: '2025-11-20T08:00:00.000Z', surgeryName: '脑室腹腔分流术', preOpPressure: 21, primaryDisease: '外伤后脑积水', hydrocephalusTypes: ['交通性'], devices: [ { implantCatalogId: adjustableCatalog.id, shuntMode: 'LPS', proximalPunctureAreas: ['腰穿'], valvePlacementSites: ['腰背部'], distalShuntDirection: '腹腔', initialPressure: '1', implantNotes: 'Seed A3 当前在用设备', labelImageUrl: 'https://seed.example.com/labels/a3-003.jpg', }, ], }, }); const patientB1 = await createWithToken(server, doctorBToken, '/b/patients', { name: 'Seed Patient B1', inpatientNo: 'ZYH-B-0001', projectName: '脑积水随访项目-B', phone: SHARED_PATIENT_IDENTITY.phone, idCard: SHARED_PATIENT_IDENTITY.idCard, doctorId: doctorB.id, initialSurgery: { surgeryDate: '2025-10-05T08:00:00.000Z', surgeryName: '脑室腹腔分流术', preOpPressure: 23, primaryDisease: '出血后脑积水', hydrocephalusTypes: ['高压性'], devices: [ { implantCatalogId: adjustableCatalog.id, shuntMode: 'VPS', proximalPunctureAreas: ['额角'], valvePlacementSites: ['耳后'], distalShuntDirection: '腹腔', initialPressure: '1', implantNotes: 'Seed B1 当前在用设备', labelImageUrl: 'https://seed.example.com/labels/b1-001.jpg', }, ], }, }); const deviceA1Id = revisionA1.devices?.[0]?.id; const deviceB1Id = patientB1.devices?.[0]?.id; if (!deviceA1Id || !deviceB1Id) { throw new Error('failed to create seed active devices'); } const publishedA = await createWithToken( server, doctorAToken, '/b/tasks/publish', { items: [ { deviceId: deviceA1Id, targetPressure: '1.5', }, ], }, ); await createWithToken(server, engineerAToken, '/b/tasks/accept', { taskId: publishedA.id, }); await createWithToken(server, engineerAToken, '/b/tasks/complete', { taskId: publishedA.id, }); await createWithToken(server, doctorBToken, '/b/tasks/publish', { items: [ { deviceId: deviceB1Id, targetPressure: '1.5', }, ], }); // 防止未使用变量被误删,保留显式访问。 void patientA2; } async function repairFixturesViaApi( app: INestApplication, prisma: PrismaService, ) { const server = app.getHttpServer(); const systemAdminToken = await loginByCredential(server, { phone: E2E_SEED_CREDENTIALS[Role.SYSTEM_ADMIN].phone, password: E2E_SEED_PASSWORD, role: Role.SYSTEM_ADMIN, }); const hospitalAdminA = await requireUserScope( prisma, OPEN_IDS.hospitalAdminA, ); const doctorA = await requireUserScope(prisma, OPEN_IDS.doctorA); const doctorA2 = await requireUserScope(prisma, OPEN_IDS.doctorA2); const doctorA3 = await requireUserScope(prisma, OPEN_IDS.doctorA3); const doctorB = await requireUserScope(prisma, OPEN_IDS.doctorB); if ( hospitalAdminA.hospitalId == null || doctorA.hospitalId == null || doctorB.hospitalId == null || doctorA.id == null || doctorA2.id == null || doctorA3.id == null || doctorB.id == null ) { throw new NotFoundException('Seed user scope is incomplete'); } const adjustableCatalog = await ensureCatalogViaApi( prisma, server, systemAdminToken, { modelCode: FIXTURE_NAMES.adjustableCatalog, manufacturer: 'Seed MedTech', name: 'Seed 可调压分流阀', isValve: true, pressureLevels: ['0.5', '1', '1.5'], notes: 'Seed 全局可调压目录样例', }, ); await ensureCatalogViaApi(prisma, server, systemAdminToken, { modelCode: FIXTURE_NAMES.highPressureCatalog, manufacturer: 'Seed MedTech', name: 'Seed 高压挡位阀', isValve: true, pressureLevels: ['10', '20', '30'], notes: 'Seed 高压挡位目录样例', }); const doctorAToken = await loginByCredential(server, { phone: E2E_SEED_CREDENTIALS[Role.DOCTOR].phone, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: doctorA.hospitalId, }); const doctorA2Token = await loginByCredential(server, { phone: EXTRA_PHONES.doctorA2, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: doctorA.hospitalId, }); const doctorA3Token = await loginByCredential(server, { phone: EXTRA_PHONES.doctorA3, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: doctorA.hospitalId, }); const doctorBToken = await loginByCredential(server, { phone: EXTRA_PHONES.doctorB, password: E2E_SEED_PASSWORD, role: Role.DOCTOR, hospitalId: doctorB.hospitalId, }); const engineerAToken = await loginByCredential(server, { phone: E2E_SEED_CREDENTIALS[Role.ENGINEER].phone, password: E2E_SEED_PASSWORD, role: Role.ENGINEER, hospitalId: doctorA.hospitalId, }); let patientA1Id = await findPatientId( prisma, doctorA.hospitalId, SHARED_PATIENT_IDENTITY.phone, SHARED_PATIENT_IDENTITY.idCard, ); if (!patientA1Id) { const patientA1 = await createWithToken( server, doctorAToken, '/b/patients', { name: 'Seed Patient A1', inpatientNo: 'ZYH-A-0001', projectName: '脑积水随访项目-A', phone: SHARED_PATIENT_IDENTITY.phone, idCard: SHARED_PATIENT_IDENTITY.idCard, doctorId: doctorA.id, initialSurgery: { surgeryDate: '2024-06-01T08:00:00.000Z', surgeryName: '首次脑室腹腔分流术', preOpPressure: 24, primaryDisease: '先天性脑积水', hydrocephalusTypes: ['交通性'], notes: '首台手术', devices: [ { implantCatalogId: adjustableCatalog.id, shuntMode: 'VPS', proximalPunctureAreas: ['额角'], valvePlacementSites: ['耳后'], distalShuntDirection: '腹腔', initialPressure: '1', implantNotes: 'Seed A1 弃用历史设备', labelImageUrl: 'https://seed.example.com/labels/a1-004.jpg', }, ], }, }, ); const oldDeviceId = patientA1.devices?.[0]?.id; if (!oldDeviceId) { throw new Error('failed to create seed old device'); } await createWithToken( server, doctorAToken, `/b/patients/${patientA1.id}/surgeries`, { surgeryDate: '2025-09-10T08:00:00.000Z', surgeryName: '分流系统翻修术', preOpPressure: 18, primaryDisease: '分流功能障碍', hydrocephalusTypes: ['交通性', '高压性'], previousShuntSurgeryDate: '2024-06-01T08:00:00.000Z', preOpMaterials: [ { type: 'IMAGE', url: 'https://seed.example.com/a1-ct-preop.png', name: 'Seed A1 术前 CT', }, ], notes: '二次手术,保留原设备历史', abandonedDeviceIds: [oldDeviceId], devices: [ { implantCatalogId: adjustableCatalog.id, shuntMode: 'VPS', proximalPunctureAreas: ['额角'], valvePlacementSites: ['耳后'], distalShuntDirection: '腹腔', initialPressure: '1', implantNotes: 'Seed A1 当前在用设备', labelImageUrl: 'https://seed.example.com/labels/a1-001.jpg', }, ], }, ); patientA1Id = patientA1.id; } if ( !(await findPatientId( prisma, doctorA.hospitalId, '13800002002', '110101199002020022', )) ) { await createWithToken(server, doctorA2Token, '/b/patients', { name: 'Seed Patient A2', inpatientNo: 'ZYH-A-0002', projectName: '脑积水随访项目-A', phone: '13800002002', idCard: '110101199002020022', doctorId: doctorA2.id, initialSurgery: { surgeryDate: '2025-12-15T08:00:00.000Z', surgeryName: '脑室腹腔分流术', preOpPressure: 20, primaryDisease: '肿瘤相关脑积水', hydrocephalusTypes: ['梗阻性'], devices: [ { implantCatalogId: adjustableCatalog.id, shuntMode: 'VPS', proximalPunctureAreas: ['枕角'], valvePlacementSites: ['胸前'], distalShuntDirection: '腹腔', initialPressure: '1', implantNotes: 'Seed A2 当前在用设备', labelImageUrl: 'https://seed.example.com/labels/a2-002.jpg', }, ], }, }); } if ( !(await findPatientId( prisma, doctorA.hospitalId, '13800002003', '110101199003030033', )) ) { await createWithToken(server, doctorA3Token, '/b/patients', { name: 'Seed Patient A3', inpatientNo: 'ZYH-A-0003', projectName: '脑积水随访项目-A', phone: '13800002003', idCard: '110101199003030033', doctorId: doctorA3.id, initialSurgery: { surgeryDate: '2025-11-20T08:00:00.000Z', surgeryName: '脑室腹腔分流术', preOpPressure: 21, primaryDisease: '外伤后脑积水', hydrocephalusTypes: ['交通性'], devices: [ { implantCatalogId: adjustableCatalog.id, shuntMode: 'LPS', proximalPunctureAreas: ['腰穿'], valvePlacementSites: ['腰背部'], distalShuntDirection: '腹腔', initialPressure: '1', implantNotes: 'Seed A3 当前在用设备', labelImageUrl: 'https://seed.example.com/labels/a3-003.jpg', }, ], }, }); } if ( !(await findPatientId( prisma, doctorB.hospitalId, SHARED_PATIENT_IDENTITY.phone, SHARED_PATIENT_IDENTITY.idCard, )) ) { await createWithToken(server, doctorBToken, '/b/patients', { name: 'Seed Patient B1', inpatientNo: 'ZYH-B-0001', projectName: '脑积水随访项目-B', phone: SHARED_PATIENT_IDENTITY.phone, idCard: SHARED_PATIENT_IDENTITY.idCard, doctorId: doctorB.id, initialSurgery: { surgeryDate: '2025-10-05T08:00:00.000Z', surgeryName: '脑室腹腔分流术', preOpPressure: 23, primaryDisease: '出血后脑积水', hydrocephalusTypes: ['高压性'], devices: [ { implantCatalogId: adjustableCatalog.id, shuntMode: 'VPS', proximalPunctureAreas: ['额角'], valvePlacementSites: ['耳后'], distalShuntDirection: '腹腔', initialPressure: '1', implantNotes: 'Seed B1 当前在用设备', labelImageUrl: 'https://seed.example.com/labels/b1-001.jpg', }, ], }, }); } const deviceA1Id = await requireDeviceId(prisma, 'Seed A1 当前在用设备'); const deviceB1Id = await requireDeviceId(prisma, 'Seed B1 当前在用设备'); if (!(await hasTaskItemForDevice(prisma, deviceA1Id))) { const publishedA = await createWithToken( server, doctorAToken, '/b/tasks/publish', { items: [ { deviceId: deviceA1Id, targetPressure: '1.5', }, ], }, ); await createWithToken(server, engineerAToken, '/b/tasks/accept', { taskId: publishedA.id, }); await createWithToken(server, engineerAToken, '/b/tasks/complete', { taskId: publishedA.id, }); } if (!(await hasTaskItemForDevice(prisma, deviceB1Id))) { await createWithToken(server, doctorBToken, '/b/tasks/publish', { items: [ { deviceId: deviceB1Id, targetPressure: '1.5', }, ], }); } void patientA1Id; } async function bootstrapDictionaries( server: ReturnType, systemAdminToken: string, ) { const dictionarySeeds = { PRIMARY_DISEASE: [ '先天性脑积水', '梗阻性脑积水', '交通性脑积水', '出血后脑积水', '肿瘤相关脑积水', '外伤后脑积水', '感染后脑积水', '分流功能障碍', ], HYDROCEPHALUS_TYPE: [ '交通性', '梗阻性', '高压性', '正常压力', '先天性', '继发性', ], SHUNT_MODE: ['VPS', 'VPLS', 'LPS', '脑室心房分流'], PROXIMAL_PUNCTURE_AREA: ['额角', '枕角', '三角区', '腰穿', '后角'], VALVE_PLACEMENT_SITE: ['耳后', '胸前', '锁骨下', '腹壁', '腰背部'], DISTAL_SHUNT_DIRECTION: ['腹腔', '胸腔', '心房', '腰大池'], } as const; for (const [type, labels] of Object.entries(dictionarySeeds)) { for (const [index, label] of labels.entries()) { await createWithToken(server, systemAdminToken, '/b/dictionaries', { type, label, sortOrder: index * 10, enabled: true, }); } } } async function createSystemAdmin( server: ReturnType, ) { const response = await request(server).post('/auth/system-admin').send({ name: 'Seed System Admin', phone: E2E_SEED_CREDENTIALS[Role.SYSTEM_ADMIN].phone, password: E2E_SEED_PASSWORD, openId: OPEN_IDS.systemAdmin, systemAdminBootstrapKey: process.env.SYSTEM_ADMIN_BOOTSTRAP_KEY, }); expectSuccessEnvelope(response, 201); } async function loginByCredential( server: ReturnType, payload: { phone: string; password: string; role: Role; hospitalId?: number; }, ) { const response = await request(server).post('/auth/login').send(payload); expectSuccessEnvelope(response, 201); return response.body.data.accessToken as string; } async function createWithToken( server: ReturnType, token: string, path: string, payload: Record, ) { const response = await request(server) .post(path) .set('Authorization', `Bearer ${token}`) .send(payload); expectSuccessEnvelope(response, 201); return response.body.data; } async function patchWithToken( server: ReturnType, token: string, path: string, payload: Record, ) { const response = await request(server) .patch(path) .set('Authorization', `Bearer ${token}`) .send(payload); expectSuccessEnvelope(response, 200); return response.body.data; } async function requireUserScope( prisma: PrismaService, openId: string, ): Promise { const user = await prisma.user.findUnique({ where: { openId }, select: { id: true, hospitalId: true, departmentId: true, groupId: true, }, }); if (!user) { throw new NotFoundException(`Seed user not found: ${openId}`); } return user; } async function requireCatalogId( prisma: PrismaService, modelCode: string, ): Promise { const catalog = await prisma.implantCatalog.findFirst({ where: { modelCode }, select: { id: true }, }); if (!catalog) { throw new NotFoundException(`Seed catalog not found: ${modelCode}`); } return catalog.id; } async function findPatientId( prisma: PrismaService, hospitalId: number, phone: string, idCard: string, ) { const patient = await prisma.patient.findFirst({ where: { hospitalId, phone, idCard }, select: { id: true }, }); return patient?.id ?? null; } async function hasTaskItemForDevice(prisma: PrismaService, deviceId: number) { const taskItem = await prisma.taskItem.findFirst({ where: { deviceId }, select: { id: true }, }); return Boolean(taskItem); } async function ensureCatalogViaApi( prisma: PrismaService, server: ReturnType, systemAdminToken: string, payload: { modelCode: string; manufacturer: string; name: string; isValve: boolean; pressureLevels: string[]; notes: string; }, ) { const existing = await prisma.implantCatalog.findFirst({ where: { modelCode: payload.modelCode }, select: { id: true }, }); if (existing) { return existing; } return createWithToken( server, systemAdminToken, '/b/devices/catalogs', payload, ); } async function requireDeviceId( prisma: PrismaService, implantNotes: string, ): Promise { const device = await prisma.device.findFirst({ where: { implantNotes }, select: { id: true }, }); if (!device) { throw new NotFoundException(`Seed device not found: ${implantNotes}`); } return device.id; } async function requirePatientId( prisma: PrismaService, hospitalId: number, phone: string, idCard: string, ): Promise { const patient = await prisma.patient.findFirst({ where: { hospitalId, phone, idCard }, select: { id: true }, }); if (!patient) { throw new NotFoundException(`Seed patient not found: ${phone}/${idCard}`); } return patient.id; } export async function loadSeedFixtures( prisma: PrismaService, ): Promise { const systemAdmin = await requireUserScope(prisma, OPEN_IDS.systemAdmin); const hospitalAdminA = await requireUserScope( prisma, OPEN_IDS.hospitalAdminA, ); const directorA = await requireUserScope(prisma, OPEN_IDS.directorA); const leaderA = await requireUserScope(prisma, OPEN_IDS.leaderA); const doctorA = await requireUserScope(prisma, OPEN_IDS.doctorA); const doctorA2 = await requireUserScope(prisma, OPEN_IDS.doctorA2); const doctorA3 = await requireUserScope(prisma, OPEN_IDS.doctorA3); const doctorB = await requireUserScope(prisma, OPEN_IDS.doctorB); const engineerA = await requireUserScope(prisma, OPEN_IDS.engineerA); const engineerB = await requireUserScope(prisma, OPEN_IDS.engineerB); const hospitalAId = hospitalAdminA.hospitalId; const hospitalBId = doctorB.hospitalId; const departmentA1Id = doctorA.departmentId; const departmentA2Id = doctorA3.departmentId; const departmentB1Id = doctorB.departmentId; const groupA1Id = doctorA.groupId; const groupA2Id = doctorA3.groupId; const groupB1Id = doctorB.groupId; if ( hospitalAId == null || hospitalBId == null || departmentA1Id == null || departmentA2Id == null || departmentB1Id == null || groupA1Id == null || groupA2Id == null || groupB1Id == null ) { throw new NotFoundException('Seed user scope is incomplete'); } return { hospitalAId, hospitalBId, departmentA1Id, departmentA2Id, departmentB1Id, groupA1Id, groupA2Id, groupB1Id, catalogs: { adjustableValveId: await requireCatalogId( prisma, FIXTURE_NAMES.adjustableCatalog, ), highPressureValveId: await requireCatalogId( prisma, FIXTURE_NAMES.highPressureCatalog, ), }, users: { systemAdminId: systemAdmin.id, hospitalAdminAId: hospitalAdminA.id, directorAId: directorA.id, leaderAId: leaderA.id, doctorAId: doctorA.id, doctorA2Id: doctorA2.id, doctorA3Id: doctorA3.id, doctorBId: doctorB.id, engineerAId: engineerA.id, engineerBId: engineerB.id, }, patients: { patientA1Id: await requirePatientId( prisma, hospitalAId, SHARED_PATIENT_IDENTITY.phone, SHARED_PATIENT_IDENTITY.idCard, ), patientA2Id: await requirePatientId( prisma, hospitalAId, '13800002002', '110101199002020022', ), patientA3Id: await requirePatientId( prisma, hospitalAId, '13800002003', '110101199003030033', ), patientB1Id: await requirePatientId( prisma, hospitalBId, SHARED_PATIENT_IDENTITY.phone, SHARED_PATIENT_IDENTITY.idCard, ), }, devices: { deviceA1Id: await requireDeviceId(prisma, 'Seed A1 当前在用设备'), deviceA2Id: await requireDeviceId(prisma, 'Seed A2 当前在用设备'), deviceA3Id: await requireDeviceId(prisma, 'Seed A3 当前在用设备'), deviceA4InactiveId: await requireDeviceId(prisma, 'Seed A1 弃用历史设备'), deviceB1Id: await requireDeviceId(prisma, 'Seed B1 当前在用设备'), }, }; }