bugfix: clinic owner user access to other (dentist)staff's treatment plans terminated.
This commit is contained in:
@@ -21,7 +21,7 @@ Frontend hook: [`frontend/src/lib/hooks/useTabBadgeCounts.ts`](frontend/src/lib/
|
|||||||
|-----|-----|----------------|
|
|-----|-----|----------------|
|
||||||
| LAB | Cases | `CASE_SENT`, `CLINIC_COMMENT`, `CASE_IMPORTANT` |
|
| LAB | Cases | `CASE_SENT`, `CLINIC_COMMENT`, `CASE_IMPORTANT` |
|
||||||
| LAB | Tasks | `TASK_COMPLETED`, `LAB_COMMENT` |
|
| LAB | Tasks | `TASK_COMPLETED`, `LAB_COMMENT` |
|
||||||
| CLINIC | Treatment | `LAB_COMMENT` (only `visibleToClinic`), `TASK_COMPLETED` |
|
| CLINIC | Treatment | `LAB_COMMENT` (only `visibleToClinic`), `TASK_COMPLETED` — **only lab cases for treatments the user provided** |
|
||||||
|
|
||||||
Counts exclude events where `actorUserId === current user`. Clinic `LAB_COMMENT` counts only when `payload.visibleToClinic === true`.
|
Counts exclude events where `actorUserId === current user`. Clinic `LAB_COMMENT` counts only when `payload.visibleToClinic === true`.
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Thin route: `app/[locale]/(dashboard)/treatment/page.tsx` (supports `?appointmen
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
`GET /treatments/patients/:id/history` returns saved treatments for **that patient** (not the whole day’s schedule). Non-owners see plans where `Treatment.providerUserId` or linked `Appointment.providerUserId` matches the logged-in user; org owners see all saved plans for the patient. New saves set `Treatment.providerUserId` to the logged-in clinician.
|
`GET /treatments/patients/:id/history` returns saved treatments for **that patient** scoped to the **logged-in clinician** (`Treatment.providerUserId` or linked `Appointment.providerUserId`). **Owners are not exempt** — each user only sees plans they created or own via their appointments.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ canEditTreatmentForDay = canEdit && selectedAppointment && !isViewingPastDay &&
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Filter in **backend** `listPatientHistory` on patient + org; provider scoping for non-owners. History is **per selected patient**, not per day or all appointments on the strip.
|
Filter in **backend** `listPatientHistory` / lab-case lists on patient + org + **provider scope** (`common/treatment-provider-scope.ts`). History is **per selected patient and per clinician**, not per day or all org plans.
|
||||||
|
|
||||||
**UI filters** (not shipped, date) are client-side only — do not add API params unless product explicitly requires server-side filtering.
|
**UI filters** (not shipped, date) are client-side only — do not add API params unless product explicitly requires server-side filtering.
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ frontend/src/
|
|||||||
**Treatment lab rules (quick ref):**
|
**Treatment lab rules (quick ref):**
|
||||||
- Lab-dependent details (e.g. prosthesis) **without teeth** can save but **cannot ship** — show `LabShipmentBlockedNotice` + inline banner; toast on dispatch add.
|
- Lab-dependent details (e.g. prosthesis) **without teeth** can save but **cannot ship** — show `LabShipmentBlockedNotice` + inline banner; toast on dispatch add.
|
||||||
- **History filters** are client-side only (`treatmentHistoryFilters.ts`): “Not shipped to lab” + single date on already-fetched patient history; includes live current draft when filtering.
|
- **History filters** are client-side only (`treatmentHistoryFilters.ts`): “Not shipped to lab” + single date on already-fetched patient history; includes live current draft when filtering.
|
||||||
- **Lab shipments rail**: unified list with scope toggle **This patient** vs **All updates** (unread across org, includes patient name).
|
- **Lab shipments rail**: unified list with scope toggle **This patient** vs **All updates** (unread across org for **this clinician's cases only**, includes patient name).
|
||||||
- **Unread semantics**: Treatment tab badge = count of unread cases (per-case read cursor) and clears when a case is opened/marked read (not on tab visit).
|
- **Unread semantics**: Treatment tab badge = count of unread cases **for the user's own treatment plans** (per-case read cursor) and clears when a case is opened/marked read (not on tab visit).
|
||||||
- **Lab shipment progress + comments**: shown in **Lab dispatch panel** for the active shipment; expanding activity / opening comments marks that case read.
|
- **Lab shipment progress + comments**: shown in **Lab dispatch panel** for the active shipment; expanding activity / opening comments marks that case read.
|
||||||
|
|
||||||
**Lab Tasks tab:** Newest case first; steps ordered 1→N; case grouping when sorted by date; `stepCompleted` filter; prosthesis colors from catalog; task assignment in **Cases** (compact row: status + assignee + last update); on **Tasks**, all staff see every task but only assignee (or unassigned pool) can change status — others see “Assigned to {name}” instead of the status dropdown; **case due dates** set/edited in clinic Treatment lab dispatch, shown on lab Cases/Tasks with overdue filter + sort; **mobile:** larger task status controls, sticky case header when grouped; **tab badges:** `LabCaseActivity` + `GET /notifications/tab-counts` (lab Cases/Tasks split, clinic Treatment) — see `.cursor/skills/lab-tasks/SKILL.md` and `.cursor/skills/lab-notifications/SKILL.md`.
|
**Lab Tasks tab:** Newest case first; steps ordered 1→N; case grouping when sorted by date; `stepCompleted` filter; prosthesis colors from catalog; task assignment in **Cases** (compact row: status + assignee + last update); on **Tasks**, all staff see every task but only assignee (or unassigned pool) can change status — others see “Assigned to {name}” instead of the status dropdown; **case due dates** set/edited in clinic Treatment lab dispatch, shown on lab Cases/Tasks with overdue filter + sort; **mobile:** larger task status controls, sticky case header when grouped; **tab badges:** `LabCaseActivity` + `GET /notifications/tab-counts` (lab Cases/Tasks split, clinic Treatment) — see `.cursor/skills/lab-tasks/SKILL.md` and `.cursor/skills/lab-notifications/SKILL.md`.
|
||||||
|
|||||||
26
backend/src/common/treatment-provider-scope.ts
Normal file
26
backend/src/common/treatment-provider-scope.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
|
/** Treatments the clinician owns — by plan provider or linked appointment provider. */
|
||||||
|
export function treatmentProviderScopeWhere(
|
||||||
|
actorUserId: string,
|
||||||
|
): Pick<Prisma.TreatmentWhereInput, 'OR'> {
|
||||||
|
return {
|
||||||
|
OR: [
|
||||||
|
{ providerUserId: actorUserId },
|
||||||
|
{ appointment: { is: { providerUserId: actorUserId } } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreatmentProviderRow = {
|
||||||
|
providerUserId: string | null;
|
||||||
|
appointment?: { providerUserId: string } | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isActorTreatmentProvider(
|
||||||
|
treatment: TreatmentProviderRow,
|
||||||
|
actorUserId: string,
|
||||||
|
): boolean {
|
||||||
|
if (treatment.providerUserId === actorUserId) return true;
|
||||||
|
return treatment.appointment?.providerUserId === actorUserId;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { LabCaseCommentSide, LabCaseActivityType, Prisma } from '@prisma/client'
|
|||||||
import { PrismaService } from '../../../prisma/prisma.service';
|
import { PrismaService } from '../../../prisma/prisma.service';
|
||||||
import { CreateLabCaseCommentDto } from './dto/lab-case-comment.dto';
|
import { CreateLabCaseCommentDto } from './dto/lab-case-comment.dto';
|
||||||
import { hasEffectivePermission } from '../../common/membership-permissions';
|
import { hasEffectivePermission } from '../../common/membership-permissions';
|
||||||
|
import { treatmentProviderScopeWhere } from '../../common/treatment-provider-scope';
|
||||||
import { LabCaseActivityService } from '../notifications/lab-case-activity.service';
|
import { LabCaseActivityService } from '../notifications/lab-case-activity.service';
|
||||||
|
|
||||||
const commentInclude = {
|
const commentInclude = {
|
||||||
@@ -273,7 +274,20 @@ export class LabCaseCommentsService {
|
|||||||
clinicOrganizationId: string,
|
clinicOrganizationId: string,
|
||||||
actorUserId: string,
|
actorUserId: string,
|
||||||
) {
|
) {
|
||||||
await this.assertClinicOwnsCase(caseId, clinicOrganizationId);
|
const labCase = await this.prisma.labCase.findFirst({
|
||||||
|
where: {
|
||||||
|
id: caseId,
|
||||||
|
treatment: {
|
||||||
|
organizationId: clinicOrganizationId,
|
||||||
|
...treatmentProviderScopeWhere(actorUserId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (!labCase) {
|
||||||
|
throw new NotFoundException('Case not found');
|
||||||
|
}
|
||||||
|
|
||||||
const membership = await this.prisma.membership.findFirst({
|
const membership = await this.prisma.membership.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: actorUserId,
|
userId: actorUserId,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
LAB_CASES_TAB_ACTIVITY_TYPES,
|
LAB_CASES_TAB_ACTIVITY_TYPES,
|
||||||
LAB_TASKS_TAB_ACTIVITY_TYPES,
|
LAB_TASKS_TAB_ACTIVITY_TYPES,
|
||||||
} from '../../common/lab-case-activity';
|
} from '../../common/lab-case-activity';
|
||||||
|
import { treatmentProviderScopeWhere } from '../../common/treatment-provider-scope';
|
||||||
|
|
||||||
type TxClient = Prisma.TransactionClient;
|
type TxClient = Prisma.TransactionClient;
|
||||||
|
|
||||||
@@ -126,7 +127,10 @@ export class LabCaseActivityService {
|
|||||||
CLINIC_TREATMENT_TAB_ACTIVITY_TYPES,
|
CLINIC_TREATMENT_TAB_ACTIVITY_TYPES,
|
||||||
{
|
{
|
||||||
sentAt: { not: null },
|
sentAt: { not: null },
|
||||||
treatment: { organizationId },
|
treatment: {
|
||||||
|
organizationId,
|
||||||
|
...treatmentProviderScopeWhere(userId),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'CLINIC',
|
'CLINIC',
|
||||||
);
|
);
|
||||||
@@ -448,7 +452,11 @@ export class LabCaseActivityService {
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
id: labCaseId,
|
id: labCaseId,
|
||||||
treatment: { organizationId },
|
sentAt: { not: null },
|
||||||
|
treatment: {
|
||||||
|
organizationId,
|
||||||
|
...treatmentProviderScopeWhere(userId),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ import {
|
|||||||
} from './treatment.utils';
|
} from './treatment.utils';
|
||||||
import { assertCompleteToothProsthesisMap } from './lab-case-send.validation';
|
import { assertCompleteToothProsthesisMap } from './lab-case-send.validation';
|
||||||
import { hasEffectivePermission } from '../../common/membership-permissions';
|
import { hasEffectivePermission } from '../../common/membership-permissions';
|
||||||
|
import {
|
||||||
|
isActorTreatmentProvider,
|
||||||
|
treatmentProviderScopeWhere,
|
||||||
|
} from '../../common/treatment-provider-scope';
|
||||||
|
|
||||||
const sentLabCaseInclude = {
|
const sentLabCaseInclude = {
|
||||||
treatment: {
|
treatment: {
|
||||||
@@ -164,22 +168,12 @@ export class TreatmentsService {
|
|||||||
await this.assertCanReadTreatment(actorUserId, organizationId);
|
await this.assertCanReadTreatment(actorUserId, organizationId);
|
||||||
await this.ensurePatientExists(patientId);
|
await this.ensurePatientExists(patientId);
|
||||||
|
|
||||||
const membership = await this.getMembership(actorUserId, organizationId);
|
|
||||||
const isOwner = membership?.isOwner ?? false;
|
|
||||||
|
|
||||||
const items = await this.prisma.treatment.findMany({
|
const items = await this.prisma.treatment.findMany({
|
||||||
where: {
|
where: {
|
||||||
patientId,
|
patientId,
|
||||||
organizationId,
|
organizationId,
|
||||||
details: { some: {} },
|
details: { some: {} },
|
||||||
...(isOwner
|
...treatmentProviderScopeWhere(actorUserId),
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
OR: [
|
|
||||||
{ providerUserId: actorUserId },
|
|
||||||
{ appointment: { is: { providerUserId: actorUserId } } },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
include: treatmentInclude,
|
include: treatmentInclude,
|
||||||
orderBy: [{ treatmentAt: 'desc' }, { createdAt: 'desc' }],
|
orderBy: [{ treatmentAt: 'desc' }, { createdAt: 'desc' }],
|
||||||
@@ -197,16 +191,13 @@ export class TreatmentsService {
|
|||||||
await this.assertCanReadTreatment(actorUserId, organizationId);
|
await this.assertCanReadTreatment(actorUserId, organizationId);
|
||||||
await this.ensurePatientExists(patientId);
|
await this.ensurePatientExists(patientId);
|
||||||
|
|
||||||
const membership = await this.getMembership(actorUserId, organizationId);
|
|
||||||
const isOwner = membership?.isOwner ?? false;
|
|
||||||
|
|
||||||
const labCases = await this.prisma.labCase.findMany({
|
const labCases = await this.prisma.labCase.findMany({
|
||||||
where: {
|
where: {
|
||||||
sentAt: { not: null },
|
sentAt: { not: null },
|
||||||
treatment: {
|
treatment: {
|
||||||
patientId,
|
patientId,
|
||||||
organizationId,
|
organizationId,
|
||||||
...this.treatmentAccessFilter(isOwner, actorUserId),
|
...treatmentProviderScopeWhere(actorUserId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: sentLabCaseInclude,
|
include: sentLabCaseInclude,
|
||||||
@@ -224,15 +215,12 @@ export class TreatmentsService {
|
|||||||
async listUnreadLabCases(organizationId: string, actorUserId: string) {
|
async listUnreadLabCases(organizationId: string, actorUserId: string) {
|
||||||
await this.assertCanReadTreatment(actorUserId, organizationId);
|
await this.assertCanReadTreatment(actorUserId, organizationId);
|
||||||
|
|
||||||
const membership = await this.getMembership(actorUserId, organizationId);
|
|
||||||
const isOwner = membership?.isOwner ?? false;
|
|
||||||
|
|
||||||
const labCases = await this.prisma.labCase.findMany({
|
const labCases = await this.prisma.labCase.findMany({
|
||||||
where: {
|
where: {
|
||||||
sentAt: { not: null },
|
sentAt: { not: null },
|
||||||
treatment: {
|
treatment: {
|
||||||
organizationId,
|
organizationId,
|
||||||
...this.treatmentAccessFilter(isOwner, actorUserId),
|
...treatmentProviderScopeWhere(actorUserId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: sentLabCaseInclude,
|
include: sentLabCaseInclude,
|
||||||
@@ -258,17 +246,6 @@ export class TreatmentsService {
|
|||||||
return { success: true, data: this.sortLabCaseSummaries(summaries) };
|
return { success: true, data: this.sortLabCaseSummaries(summaries) };
|
||||||
}
|
}
|
||||||
|
|
||||||
private treatmentAccessFilter(isOwner: boolean, actorUserId: string) {
|
|
||||||
return isOwner
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
OR: [
|
|
||||||
{ providerUserId: actorUserId },
|
|
||||||
{ appointment: { is: { providerUserId: actorUserId } } },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async mapSentLabCaseSummaries(
|
private async mapSentLabCaseSummaries(
|
||||||
labCases: SentLabCaseRow[],
|
labCases: SentLabCaseRow[],
|
||||||
actorUserId: string,
|
actorUserId: string,
|
||||||
@@ -367,7 +344,6 @@ export class TreatmentsService {
|
|||||||
appointmentId,
|
appointmentId,
|
||||||
organizationId,
|
organizationId,
|
||||||
actorUserId,
|
actorUserId,
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const treatment = await this.prisma.treatment.findFirst({
|
const treatment = await this.prisma.treatment.findFirst({
|
||||||
@@ -392,7 +368,6 @@ export class TreatmentsService {
|
|||||||
appointmentId,
|
appointmentId,
|
||||||
organizationId,
|
organizationId,
|
||||||
actorUserId,
|
actorUserId,
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const d of dto.details) {
|
for (const d of dto.details) {
|
||||||
@@ -537,7 +512,6 @@ export class TreatmentsService {
|
|||||||
appointmentId,
|
appointmentId,
|
||||||
organizationId,
|
organizationId,
|
||||||
actorUserId,
|
actorUserId,
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const treatment = await this.prisma.treatment.findFirst({
|
const treatment = await this.prisma.treatment.findFirst({
|
||||||
@@ -615,7 +589,13 @@ export class TreatmentsService {
|
|||||||
for (const [index, lc] of dto.labCases.entries()) {
|
for (const [index, lc] of dto.labCases.entries()) {
|
||||||
if (lc.id && sentLabCaseIds.has(lc.id)) {
|
if (lc.id && sentLabCaseIds.has(lc.id)) {
|
||||||
if (lc.dueDate !== undefined) {
|
if (lc.dueDate !== undefined) {
|
||||||
await this.updateLabCaseDueDateInTx(tx, lc.id, organizationId, lc.dueDate);
|
await this.updateLabCaseDueDateInTx(
|
||||||
|
tx,
|
||||||
|
lc.id,
|
||||||
|
organizationId,
|
||||||
|
actorUserId,
|
||||||
|
lc.dueDate,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -710,7 +690,12 @@ export class TreatmentsService {
|
|||||||
treatment: { organizationId },
|
treatment: { organizationId },
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
treatment: { select: { providerUserId: true } },
|
treatment: {
|
||||||
|
select: {
|
||||||
|
providerUserId: true,
|
||||||
|
appointment: { select: { providerUserId: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
sends: { select: { organizationId: true } },
|
sends: { select: { organizationId: true } },
|
||||||
details: {
|
details: {
|
||||||
include: {
|
include: {
|
||||||
@@ -739,11 +724,8 @@ export class TreatmentsService {
|
|||||||
|
|
||||||
assertCompleteToothProsthesisMap(labCase);
|
assertCompleteToothProsthesisMap(labCase);
|
||||||
|
|
||||||
if (labCase.treatment.providerUserId !== actorUserId) {
|
if (!isActorTreatmentProvider(labCase.treatment, actorUserId)) {
|
||||||
const membership = await this.getMembership(actorUserId, organizationId);
|
throw new ForbiddenException('Only the treatment provider can send this lab case');
|
||||||
if (!membership?.isOwner) {
|
|
||||||
throw new ForbiddenException('Only the appointment provider can send this lab case');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkedOrgIds = await this.getActiveLinkedOrganizationIds(organizationId);
|
const linkedOrgIds = await this.getActiveLinkedOrganizationIds(organizationId);
|
||||||
@@ -821,7 +803,13 @@ export class TreatmentsService {
|
|||||||
await this.assertCanEditTreatment(actorUserId, organizationId);
|
await this.assertCanEditTreatment(actorUserId, organizationId);
|
||||||
|
|
||||||
const updated = await this.prisma.$transaction(async (tx) => {
|
const updated = await this.prisma.$transaction(async (tx) => {
|
||||||
await this.updateLabCaseDueDateInTx(tx, labCaseId, organizationId, dto.dueDate ?? null);
|
await this.updateLabCaseDueDateInTx(
|
||||||
|
tx,
|
||||||
|
labCaseId,
|
||||||
|
organizationId,
|
||||||
|
actorUserId,
|
||||||
|
dto.dueDate ?? null,
|
||||||
|
);
|
||||||
return tx.labCase.findFirstOrThrow({
|
return tx.labCase.findFirstOrThrow({
|
||||||
where: { id: labCaseId },
|
where: { id: labCaseId },
|
||||||
include: {
|
include: {
|
||||||
@@ -862,12 +850,16 @@ export class TreatmentsService {
|
|||||||
tx: Prisma.TransactionClient,
|
tx: Prisma.TransactionClient,
|
||||||
labCaseId: string,
|
labCaseId: string,
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
|
actorUserId: string,
|
||||||
dueDateInput?: string | null,
|
dueDateInput?: string | null,
|
||||||
) {
|
) {
|
||||||
const labCase = await tx.labCase.findFirst({
|
const labCase = await tx.labCase.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: labCaseId,
|
id: labCaseId,
|
||||||
treatment: { organizationId },
|
treatment: {
|
||||||
|
organizationId,
|
||||||
|
...treatmentProviderScopeWhere(actorUserId),
|
||||||
|
},
|
||||||
sentAt: { not: null },
|
sentAt: { not: null },
|
||||||
},
|
},
|
||||||
include: { tasks: { select: { status: true } } },
|
include: { tasks: { select: { status: true } } },
|
||||||
@@ -902,7 +894,7 @@ export class TreatmentsService {
|
|||||||
actorUserId: string,
|
actorUserId: string,
|
||||||
) {
|
) {
|
||||||
await this.assertCanEditTreatment(actorUserId, organizationId);
|
await this.assertCanEditTreatment(actorUserId, organizationId);
|
||||||
await this.ensureAppointmentProvider(appointmentId, organizationId, actorUserId, true);
|
await this.ensureAppointmentProvider(appointmentId, organizationId, actorUserId);
|
||||||
|
|
||||||
if (!detailClientKey?.trim()) {
|
if (!detailClientKey?.trim()) {
|
||||||
throw new BadRequestException('detailClientKey is required');
|
throw new BadRequestException('detailClientKey is required');
|
||||||
@@ -956,7 +948,14 @@ export class TreatmentsService {
|
|||||||
where: {
|
where: {
|
||||||
id: attachmentId,
|
id: attachmentId,
|
||||||
OR: [
|
OR: [
|
||||||
{ detail: { treatment: { organizationId } } },
|
{
|
||||||
|
detail: {
|
||||||
|
treatment: {
|
||||||
|
organizationId,
|
||||||
|
...treatmentProviderScopeWhere(actorUserId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{ appointmentId: { not: null } },
|
{ appointmentId: { not: null } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -975,7 +974,11 @@ export class TreatmentsService {
|
|||||||
|
|
||||||
if (!attachment.detail && attachment.appointmentId) {
|
if (!attachment.detail && attachment.appointmentId) {
|
||||||
const appointment = await this.prisma.appointment.findFirst({
|
const appointment = await this.prisma.appointment.findFirst({
|
||||||
where: { id: attachment.appointmentId, organizationId },
|
where: {
|
||||||
|
id: attachment.appointmentId,
|
||||||
|
organizationId,
|
||||||
|
providerUserId: actorUserId,
|
||||||
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
if (!appointment) {
|
if (!appointment) {
|
||||||
@@ -1228,7 +1231,6 @@ export class TreatmentsService {
|
|||||||
appointmentId: string,
|
appointmentId: string,
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
actorUserId: string,
|
actorUserId: string,
|
||||||
requireProviderMatch: boolean,
|
|
||||||
) {
|
) {
|
||||||
const appointment = await this.prisma.appointment.findFirst({
|
const appointment = await this.prisma.appointment.findFirst({
|
||||||
where: { id: appointmentId, organizationId },
|
where: { id: appointmentId, organizationId },
|
||||||
@@ -1244,12 +1246,8 @@ export class TreatmentsService {
|
|||||||
throw new NotFoundException('Appointment not found');
|
throw new NotFoundException('Appointment not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requireProviderMatch) {
|
if (appointment.providerUserId !== actorUserId) {
|
||||||
const membership = await this.getMembership(actorUserId, organizationId);
|
throw new ForbiddenException('You are not the provider for this appointment');
|
||||||
const isOwner = membership?.isOwner ?? false;
|
|
||||||
if (!isOwner && appointment.providerUserId !== actorUserId) {
|
|
||||||
throw new ForbiddenException('You are not the provider for this appointment');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return appointment;
|
return appointment;
|
||||||
|
|||||||
Reference in New Issue
Block a user