import { BadRequestException, ForbiddenException, Injectable, NotFoundException, } from '@nestjs/common'; import { Prisma } from '../generated/prisma/client.js'; import { Role } from '../generated/prisma/enums.js'; import type { ActorContext } from '../common/actor-context.js'; import { MESSAGES } from '../common/messages.js'; import { PrismaService } from '../prisma.service.js'; import { OrganizationAccessService } from '../organization-common/organization-access.service.js'; import { CreateDepartmentDto } from './dto/create-department.dto.js'; import { UpdateDepartmentDto } from './dto/update-department.dto.js'; import { OrganizationQueryDto } from '../organization-common/dto/organization-query.dto.js'; /** * 科室资源服务:聚焦科室实体 CRUD 与医院作用域限制。 */ @Injectable() export class DepartmentsService { constructor( private readonly prisma: PrismaService, private readonly access: OrganizationAccessService, ) {} /** * 创建科室:系统管理员可跨院创建;院管仅可创建本院科室。 */ async create(actor: ActorContext, dto: CreateDepartmentDto) { this.access.assertRole(actor, [Role.SYSTEM_ADMIN, Role.HOSPITAL_ADMIN]); const hospitalId = this.access.toInt( dto.hospitalId, MESSAGES.ORG.HOSPITAL_ID_REQUIRED, ); await this.access.ensureHospitalExists(hospitalId); this.access.assertHospitalScope(actor, hospitalId); return this.prisma.department.create({ data: { name: this.access.normalizeName( dto.name, MESSAGES.ORG.DEPARTMENT_NAME_REQUIRED, ), hospitalId, }, }); } /** * 查询科室列表:院管限定本院。 */ async findAll(actor: ActorContext, query: OrganizationQueryDto) { this.access.assertRole(actor, [ Role.SYSTEM_ADMIN, Role.HOSPITAL_ADMIN, Role.DIRECTOR, Role.LEADER, Role.DOCTOR, ]); const paging = this.access.resolvePaging(query); const where: Prisma.DepartmentWhereInput = {}; if (query.keyword) { where.name = { contains: query.keyword.trim(), mode: 'insensitive' }; } if (actor.role === Role.HOSPITAL_ADMIN) { where.hospitalId = this.access.requireActorHospitalId(actor); } else if ( actor.role === Role.DIRECTOR || actor.role === Role.LEADER || actor.role === Role.DOCTOR ) { where.id = this.access.requireActorDepartmentId(actor); } else if (query.hospitalId != null) { where.hospitalId = this.access.toInt( query.hospitalId, MESSAGES.ORG.HOSPITAL_ID_REQUIRED, ); } const [total, list] = await this.prisma.$transaction([ this.prisma.department.count({ where }), this.prisma.department.findMany({ where, include: { hospital: true, _count: { select: { users: true, groups: true } }, }, skip: paging.skip, take: paging.take, orderBy: { id: 'desc' }, }), ]); return { total, ...paging, list }; } /** * 查询科室详情:院管仅可查看本院。 */ async findOne(actor: ActorContext, id: number) { this.access.assertRole(actor, [ Role.SYSTEM_ADMIN, Role.HOSPITAL_ADMIN, Role.DIRECTOR, Role.LEADER, Role.DOCTOR, ]); const departmentId = this.access.toInt( id, MESSAGES.ORG.DEPARTMENT_ID_REQUIRED, ); const department = await this.prisma.department.findUnique({ where: { id: departmentId }, include: { hospital: true, _count: { select: { users: true, groups: true } }, }, }); if (!department) { throw new NotFoundException(MESSAGES.ORG.DEPARTMENT_NOT_FOUND); } if (actor.role === Role.HOSPITAL_ADMIN) { this.access.assertHospitalScope(actor, department.hospitalId); } else if ( actor.role === Role.DIRECTOR || actor.role === Role.LEADER || actor.role === Role.DOCTOR ) { const actorDepartmentId = this.access.requireActorDepartmentId(actor); if (department.id !== actorDepartmentId) { throw new ForbiddenException(MESSAGES.ORG.HOSPITAL_ADMIN_SCOPE_INVALID); } } return department; } /** * 更新科室:院管仅可修改本院。 */ async update(actor: ActorContext, id: number, dto: UpdateDepartmentDto) { const current = await this.findOne(actor, id); const data: Prisma.DepartmentUpdateInput = {}; if (dto.hospitalId !== undefined) { throw new BadRequestException(MESSAGES.ORG.DEPARTMENT_REPARENT_FORBIDDEN); } if (dto.name !== undefined) { data.name = this.access.normalizeName( dto.name, MESSAGES.ORG.DEPARTMENT_NAME_REQUIRED, ); } return this.prisma.department.update({ where: { id: current.id }, data, }); } /** * 删除科室:院管仅可删本院科室。 */ async remove(actor: ActorContext, id: number) { this.access.assertRole(actor, [Role.SYSTEM_ADMIN, Role.HOSPITAL_ADMIN]); const current = await this.findOne(actor, id); try { return await this.prisma.department.delete({ where: { id: current.id } }); } catch (error) { this.access.handleDeleteConflict(error); throw error; } } }