调压任务收束
This commit is contained in:
parent
6a3eb49ab6
commit
21941e94fd
@ -84,7 +84,7 @@ export class TaskService {
|
||||
const page = query.page ?? 1;
|
||||
const pageSize = query.pageSize ?? 20;
|
||||
const skip = (page - 1) * pageSize;
|
||||
const where = this.buildTaskRecordWhere(query, hospitalId);
|
||||
const where = this.buildTaskRecordWhere(actor, query, hospitalId);
|
||||
|
||||
const [total, items] = await Promise.all([
|
||||
this.prisma.taskItem.count({ where }),
|
||||
@ -608,16 +608,23 @@ export class TaskService {
|
||||
* 构造调压记录查询条件。
|
||||
*/
|
||||
private buildTaskRecordWhere(
|
||||
actor: ActorContext,
|
||||
query: TaskRecordQueryDto,
|
||||
hospitalId?: number,
|
||||
): Prisma.TaskItemWhereInput {
|
||||
const keyword = query.keyword?.trim();
|
||||
const patientScope = this.buildTaskPatientScope(actor);
|
||||
|
||||
const where: Prisma.TaskItemWhereInput = {
|
||||
task: {
|
||||
hospitalId,
|
||||
status: query.status,
|
||||
},
|
||||
device: patientScope
|
||||
? {
|
||||
patient: patientScope,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
if (!keyword) {
|
||||
@ -676,6 +683,42 @@ export class TaskService {
|
||||
return where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按角色构造任务列表中的患者可见范围,保持与患者列表一致。
|
||||
*/
|
||||
private buildTaskPatientScope(actor: ActorContext): Prisma.PatientWhereInput | null {
|
||||
switch (actor.role) {
|
||||
case Role.SYSTEM_ADMIN:
|
||||
case Role.HOSPITAL_ADMIN:
|
||||
case Role.ENGINEER:
|
||||
return null;
|
||||
case Role.DOCTOR:
|
||||
return {
|
||||
doctorId: actor.id,
|
||||
};
|
||||
case Role.LEADER:
|
||||
if (!actor.groupId) {
|
||||
throw new BadRequestException(MESSAGES.PATIENT.GROUP_REQUIRED);
|
||||
}
|
||||
return {
|
||||
doctor: {
|
||||
groupId: actor.groupId,
|
||||
},
|
||||
};
|
||||
case Role.DIRECTOR:
|
||||
if (!actor.departmentId) {
|
||||
throw new BadRequestException(MESSAGES.PATIENT.DEPARTMENT_REQUIRED);
|
||||
}
|
||||
return {
|
||||
doctor: {
|
||||
departmentId: actor.departmentId,
|
||||
},
|
||||
};
|
||||
default:
|
||||
throw new ForbiddenException(MESSAGES.TASK.ACTOR_ROLE_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调压目标挡位标准化。
|
||||
*/
|
||||
|
||||
@ -26,6 +26,7 @@ describe('BTasksController (e2e)', () => {
|
||||
let ctx: E2EContext;
|
||||
let samplePngBuffer: Buffer;
|
||||
let doctorBToken = '';
|
||||
let doctorA2Token = '';
|
||||
|
||||
beforeAll(async () => {
|
||||
ctx = await createE2EContext();
|
||||
@ -44,6 +45,11 @@ describe('BTasksController (e2e)', () => {
|
||||
Role.DOCTOR,
|
||||
ctx.fixtures.hospitalBId,
|
||||
);
|
||||
doctorA2Token = await loginByUser(
|
||||
ctx.fixtures.users.doctorA2Id,
|
||||
Role.DOCTOR,
|
||||
ctx.fixtures.hospitalAId,
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -293,21 +299,53 @@ describe('BTasksController (e2e)', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('成功:DOCTOR 仅可查看本院调压记录', async () => {
|
||||
it('成功:DOCTOR 仅可查看本人患者调压记录', async () => {
|
||||
const keywordPrefix = uniqueSeedValue('scope-doctor');
|
||||
const [selfDevice] = await createAdjustableDevices({
|
||||
actorToken: ctx.tokens[Role.DOCTOR],
|
||||
doctorId: ctx.fixtures.users.doctorAId,
|
||||
patientName: `${keywordPrefix}-self`,
|
||||
});
|
||||
const [peerDevice] = await createAdjustableDevices({
|
||||
actorToken: doctorA2Token,
|
||||
doctorId: ctx.fixtures.users.doctorA2Id,
|
||||
patientName: `${keywordPrefix}-peer`,
|
||||
});
|
||||
|
||||
await publishPendingTask(
|
||||
selfDevice.id,
|
||||
'1.5',
|
||||
ctx.tokens[Role.SYSTEM_ADMIN],
|
||||
);
|
||||
await publishPendingTask(
|
||||
peerDevice.id,
|
||||
'1.5',
|
||||
ctx.tokens[Role.SYSTEM_ADMIN],
|
||||
);
|
||||
|
||||
const response = await request(ctx.app.getHttpServer())
|
||||
.get('/b/tasks')
|
||||
.set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
|
||||
.query({ page: 1, pageSize: 20 });
|
||||
.query({ page: 1, pageSize: 50, keyword: keywordPrefix });
|
||||
|
||||
expectSuccessEnvelope(response, 200);
|
||||
expect(Array.isArray(response.body.data.list)).toBe(true);
|
||||
expect(response.body.data.total).toBeGreaterThan(0);
|
||||
expect(
|
||||
response.body.data.list.every(
|
||||
(item: { hospital?: { id?: number } }) =>
|
||||
item.hospital?.id === ctx.fixtures.hospitalAId,
|
||||
(item: {
|
||||
hospital?: { id?: number };
|
||||
patient?: { name?: string };
|
||||
}) =>
|
||||
item.hospital?.id === ctx.fixtures.hospitalAId &&
|
||||
item.patient?.name?.includes(`${keywordPrefix}-self`),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
response.body.data.list.some((item: { patient?: { name?: string } }) =>
|
||||
item.patient?.name?.includes(`${keywordPrefix}-peer`),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('失败:hospitalId 非法返回 400', async () => {
|
||||
|
||||
@ -78,7 +78,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="phone" label="联系电话" min-width="140" />
|
||||
<el-table-column prop="idCard" label="身份证号" min-width="200" />
|
||||
<el-table-column label="归属医院" min-width="150">
|
||||
<el-table-column v-if="canViewHospitalInfo" label="归属医院" min-width="150">
|
||||
<template #default="{ row }">
|
||||
{{ row.hospital?.name || '-' }}
|
||||
</template>
|
||||
@ -421,7 +421,7 @@
|
||||
<el-descriptions-item label="创建人">
|
||||
{{ detailPatient.creator?.name || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="归属医院">
|
||||
<el-descriptions-item v-if="canViewHospitalInfo" label="归属医院">
|
||||
{{ detailPatient.hospital?.name || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="建档时间">
|
||||
@ -716,7 +716,7 @@
|
||||
{{ row.surgery?.surgeryName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="医院" min-width="150">
|
||||
<el-table-column v-if="canViewHospitalInfo" label="医院" min-width="150">
|
||||
<template #default="{ row }">
|
||||
{{ row.hospital?.name || '-' }}
|
||||
</template>
|
||||
@ -791,6 +791,9 @@ const doctorTreeProps = {
|
||||
};
|
||||
|
||||
const isSystemAdmin = computed(() => userStore.role === 'SYSTEM_ADMIN');
|
||||
const canViewHospitalInfo = computed(() =>
|
||||
['SYSTEM_ADMIN', 'HOSPITAL_ADMIN'].includes(userStore.role),
|
||||
);
|
||||
const canPublishAdjustTask = computed(() =>
|
||||
['SYSTEM_ADMIN', 'HOSPITAL_ADMIN', 'DOCTOR', 'DIRECTOR', 'LEADER'].includes(
|
||||
userStore.role,
|
||||
|
||||
@ -193,13 +193,7 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isEngineer"
|
||||
label="操作"
|
||||
width="220"
|
||||
fixed="right"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="canAccept(row)"
|
||||
@ -218,7 +212,7 @@
|
||||
:loading="actionTaskId === row.taskId && actionType === 'cancel'"
|
||||
@click="handleCancel(row)"
|
||||
>
|
||||
取消接收
|
||||
{{ getCancelButtonText(row) }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canComplete(row)"
|
||||
@ -372,6 +366,11 @@ const userStore = useUserStore();
|
||||
|
||||
const isSystemAdmin = computed(() => userStore.role === 'SYSTEM_ADMIN');
|
||||
const isEngineer = computed(() => userStore.role === 'ENGINEER');
|
||||
const canCreatorCancel = computed(() =>
|
||||
['SYSTEM_ADMIN', 'HOSPITAL_ADMIN', 'DOCTOR', 'DIRECTOR', 'LEADER'].includes(
|
||||
userStore.role,
|
||||
),
|
||||
);
|
||||
const currentUserId = computed(() => userStore.userInfo?.id || null);
|
||||
const uploadHospitalId = computed(() => userStore.userInfo?.hospitalId || null);
|
||||
const pageAlertTitle = computed(() =>
|
||||
@ -454,11 +453,27 @@ function canComplete(row) {
|
||||
}
|
||||
|
||||
function canCancel(row) {
|
||||
return (
|
||||
const canEngineerRelease =
|
||||
isEngineer.value &&
|
||||
row?.status === 'ACCEPTED' &&
|
||||
row?.engineer?.id === currentUserId.value
|
||||
);
|
||||
row?.engineer?.id === currentUserId.value;
|
||||
|
||||
const canCreatorCancelTask =
|
||||
canCreatorCancel.value &&
|
||||
row?.status &&
|
||||
['PENDING', 'ACCEPTED'].includes(row.status) &&
|
||||
row?.creator?.id === currentUserId.value;
|
||||
|
||||
return canEngineerRelease || canCreatorCancelTask;
|
||||
}
|
||||
|
||||
function getCancelButtonText(row) {
|
||||
const isEngineerRelease =
|
||||
isEngineer.value &&
|
||||
row?.status === 'ACCEPTED' &&
|
||||
row?.engineer?.id === currentUserId.value;
|
||||
|
||||
return isEngineerRelease ? '取消接收' : '取消任务';
|
||||
}
|
||||
|
||||
function isImageMaterial(material) {
|
||||
@ -580,13 +595,22 @@ function removeCompletionMaterial(index) {
|
||||
}
|
||||
|
||||
async function handleCancel(row) {
|
||||
const isEngineerRelease =
|
||||
isEngineer.value && row?.status === 'ACCEPTED' && row?.engineer?.id === currentUserId.value;
|
||||
const confirmTitle = isEngineerRelease ? '取消接收' : '取消任务';
|
||||
const confirmText = isEngineerRelease
|
||||
? '取消接收后,任务会退回待接收状态,其他同院工程师可重新接收,是否继续?'
|
||||
: '取消后任务将变更为已取消状态,是否继续?';
|
||||
const confirmButtonText = isEngineerRelease ? '确认取消接收' : '确认取消任务';
|
||||
const successText = isEngineerRelease ? '任务已退回待接收' : '任务已取消';
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'取消接收后,任务会退回待接收状态,其他同院工程师可重新接收,是否继续?',
|
||||
'取消接收',
|
||||
confirmText,
|
||||
confirmTitle,
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认取消接收',
|
||||
confirmButtonText,
|
||||
cancelButtonText: '返回',
|
||||
},
|
||||
);
|
||||
@ -598,7 +622,7 @@ async function handleCancel(row) {
|
||||
actionType.value = 'cancel';
|
||||
try {
|
||||
await cancelTask({ taskId: row.taskId });
|
||||
ElMessage.success('任务已退回待接收');
|
||||
ElMessage.success(successText);
|
||||
await fetchData();
|
||||
} finally {
|
||||
actionTaskId.value = null;
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属医院" min-width="150">
|
||||
<el-table-column v-if="canManageUsers" label="所属医院" min-width="150">
|
||||
<template #default="{ row }">
|
||||
{{ resolveHospitalName(row.hospitalId) }}
|
||||
</template>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user