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