Commit 7554f95b by luoqi

feat(tenant): P0-5 by-id 守卫补品牌隔离

患者详情(reveal/timeline)、plan 详情/分配/回收、plan-aggregate 详情:
by-id 取数也按 patient.sourceUnit 圈品牌(findUnique→findFirst + patient 过滤,
命中不到→现有 NotFound 守卫接住)。sourceUnits 空=不限(集团级角色)。
至此 §4.4 数据范围在患者粒度的品牌隔离全覆盖(搜索/列表/by-id)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 62168f4f
...@@ -101,6 +101,7 @@ export class PatientService { ...@@ -101,6 +101,7 @@ export class PatientService {
id: patientId, id: patientId,
hostId: scope.hostId, hostId: scope.hostId,
tenantId: scope.tenantId, tenantId: scope.tenantId,
...(scope.sourceUnits.length ? { sourceUnit: { in: scope.sourceUnits } } : {}),
}, },
select: { phone: true }, select: { phone: true },
}); });
...@@ -120,6 +121,7 @@ export class PatientService { ...@@ -120,6 +121,7 @@ export class PatientService {
id: patientId, id: patientId,
hostId: scope.hostId, hostId: scope.hostId,
tenantId: scope.tenantId, tenantId: scope.tenantId,
...(scope.sourceUnits.length ? { sourceUnit: { in: scope.sourceUnits } } : {}),
}, },
}); });
if (!patient) { if (!patient) {
......
...@@ -28,8 +28,12 @@ export class PlanAggregateService { ...@@ -28,8 +28,12 @@ export class PlanAggregateService {
* (app)/plans/[planId] 路由 + PlansAggregateController 的入口。 * (app)/plans/[planId] 路由 + PlansAggregateController 的入口。
*/ */
async getPlanDetailByPlanId(scope: TenantScopeContext, planId: string) { async getPlanDetailByPlanId(scope: TenantScopeContext, planId: string) {
const plan = await this.prisma.followupPlan.findUnique({ const plan = await this.prisma.followupPlan.findFirst({
where: { id: planId }, // 集团模型:by-id 也按品牌圈(plan→patient.sourceUnit)
where: {
id: planId,
...(scope.sourceUnits.length ? { patient: { sourceUnit: { in: scope.sourceUnits } } } : {}),
},
include: { reasons: { orderBy: { priorityScore: 'desc' } } }, include: { reasons: { orderBy: { priorityScore: 'desc' } } },
}); });
if (!plan) throw new NotFoundException(`Plan ${planId} not found`); if (!plan) throw new NotFoundException(`Plan ${planId} not found`);
......
...@@ -351,8 +351,12 @@ export class PlanService { ...@@ -351,8 +351,12 @@ export class PlanService {
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
async detail(scope: TenantScopeContext, planId: string): Promise<PlanDetailResponse> { async detail(scope: TenantScopeContext, planId: string): Promise<PlanDetailResponse> {
const plan = await this.prisma.followupPlan.findUnique({ const plan = await this.prisma.followupPlan.findFirst({
where: { id: planId }, // 集团模型:by-id 取 plan 也按品牌圈(plan→patient.sourceUnit);命中不到→下方 NotFound
where: {
id: planId,
...(scope.sourceUnits.length ? { patient: { sourceUnit: { in: scope.sourceUnits } } } : {}),
},
include: { reasons: { orderBy: { priorityScore: 'desc' } } }, include: { reasons: { orderBy: { priorityScore: 'desc' } } },
}); });
if (!plan || plan.hostId !== scope.hostId || plan.tenantId !== scope.tenantId) { if (!plan || plan.hostId !== scope.hostId || plan.tenantId !== scope.tenantId) {
...@@ -400,8 +404,11 @@ export class PlanService { ...@@ -400,8 +404,11 @@ export class PlanService {
assigneeUserId: string, assigneeUserId: string,
_assigneeRole?: string, _assigneeRole?: string,
): Promise<void> { ): Promise<void> {
const plan = await this.prisma.followupPlan.findUnique({ const plan = await this.prisma.followupPlan.findFirst({
where: { id: planId }, where: {
id: planId,
...(scope.sourceUnits.length ? { patient: { sourceUnit: { in: scope.sourceUnits } } } : {}),
},
select: { id: true, hostId: true, tenantId: true, status: true, assigneeUserId: true }, select: { id: true, hostId: true, tenantId: true, status: true, assigneeUserId: true },
}); });
if (!plan || plan.hostId !== scope.hostId || plan.tenantId !== scope.tenantId) { if (!plan || plan.hostId !== scope.hostId || plan.tenantId !== scope.tenantId) {
...@@ -431,8 +438,11 @@ export class PlanService { ...@@ -431,8 +438,11 @@ export class PlanService {
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
async recycle(scope: TenantScopeContext, planId: string): Promise<void> { async recycle(scope: TenantScopeContext, planId: string): Promise<void> {
const plan = await this.prisma.followupPlan.findUnique({ const plan = await this.prisma.followupPlan.findFirst({
where: { id: planId }, where: {
id: planId,
...(scope.sourceUnits.length ? { patient: { sourceUnit: { in: scope.sourceUnits } } } : {}),
},
select: { id: true, hostId: true, tenantId: true, status: true }, select: { id: true, hostId: true, tenantId: true, status: true },
}); });
if (!plan || plan.hostId !== scope.hostId || plan.tenantId !== scope.tenantId) { if (!plan || plan.hostId !== scope.hostId || plan.tenantId !== scope.tenantId) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment