新增 B 端上传接口与列表接口,统一文件上传和分页查询能力 上传能力支持医院级数据隔离:系统管理员需显式指定医院,院内角色按登录医院自动隔离 图片上传自动压缩并转为 webp,视频上传自动转码并压缩为 mp4,普通文件按原始类型存储 增加上传目录与公开访问能力,统一输出可直接预览的访问地址 前端新增影像库页面,支持按类型筛选、关键字检索、分页浏览、在线预览与原文件访问 前端新增通用上传组件,支持在页面内复用并返回上传结果 管理后台新增影像库菜单与路由,并补充页面级角色权限控制 患者手术相关表单接入上传复用能力,支持术前资料与设备标签上传回填 新增上传模块 e2e 用例,覆盖成功路径、权限矩阵与关键失败场景 补充上传模块文档与安装依赖说明,完善工程内使用说明
828 lines
23 KiB
TypeScript
828 lines
23 KiB
TypeScript
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<E2ESeedFixtures> {
|
|
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);
|
|
}
|
|
|
|
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 可调压分流阀',
|
|
pressureLevels: ['0.5', '1', '1.5'],
|
|
isPressureAdjustable: true,
|
|
notes: 'Seed 全局可调压目录样例',
|
|
},
|
|
);
|
|
await createWithToken(server, systemAdminToken, '/b/devices/catalogs', {
|
|
modelCode: FIXTURE_NAMES.highPressureCatalog,
|
|
manufacturer: 'Seed MedTech',
|
|
name: 'Seed 高压挡位阀',
|
|
pressureLevels: ['10', '20', '30'],
|
|
isPressureAdjustable: true,
|
|
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',
|
|
{
|
|
engineerId: engineerA.id,
|
|
items: [
|
|
{
|
|
deviceId: deviceA1Id,
|
|
targetPressure: '1.5',
|
|
},
|
|
],
|
|
},
|
|
);
|
|
|
|
await createWithToken(server, engineerAToken, '/b/tasks/complete', {
|
|
taskId: publishedA.id,
|
|
});
|
|
|
|
await createWithToken(server, doctorBToken, '/b/tasks/publish', {
|
|
engineerId: engineerB.id,
|
|
items: [
|
|
{
|
|
deviceId: deviceB1Id,
|
|
targetPressure: '1.5',
|
|
},
|
|
],
|
|
});
|
|
|
|
// 防止未使用变量被误删,保留显式访问。
|
|
void patientA2;
|
|
}
|
|
|
|
async function bootstrapDictionaries(
|
|
server: ReturnType<INestApplication['getHttpServer']>,
|
|
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<INestApplication['getHttpServer']>,
|
|
) {
|
|
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<INestApplication['getHttpServer']>,
|
|
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<INestApplication['getHttpServer']>,
|
|
token: string,
|
|
path: string,
|
|
payload: Record<string, unknown>,
|
|
) {
|
|
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<INestApplication['getHttpServer']>,
|
|
token: string,
|
|
path: string,
|
|
payload: Record<string, unknown>,
|
|
) {
|
|
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<SeedUserScope> {
|
|
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<number> {
|
|
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 requireDeviceId(
|
|
prisma: PrismaService,
|
|
implantNotes: string,
|
|
): Promise<number> {
|
|
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<number> {
|
|
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<E2ESeedFixtures> {
|
|
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 当前在用设备'),
|
|
},
|
|
};
|
|
}
|