backend: per-doctor patient visibility, transfer, provider picker & scoped activity

- Add patients.primary_provider_id (FK to user) + migration; persist it through
  create/update and surface it on the Patient shape.
- Scope patient list/get for the `doctor` role to their own panel (with a
  createdBy fallback for legacy rows); admin/owner/member/reception/viewer keep
  seeing every patient.
- Add POST /api/patients/:fileNumber/transfer to reassign a chart (updates the
  provider link + PCP label, records activity, notifies the clinic).
- Add GET /api/staff/providers (any member) listing clinical-capable members for
  the PCP picker and transfer dialog.
- Scope the activity feed: non-admins see only their own actions; owners/admins
  see the whole clinic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-06-09 19:51:17 +03:00
parent 4f8793c765
commit 6a0fab97ae
11 changed files with 2697 additions and 8 deletions
+14 -3
View File
@@ -1,4 +1,4 @@
import { desc, eq } from "drizzle-orm";
import { and, desc, eq } from "drizzle-orm";
import { db } from "../db/index.js";
import { activityLog } from "../db/schema/activity.js";
@@ -54,14 +54,25 @@ export async function recordActivity(params: {
}
}
// Lists the clinic's audit feed. When `actorId` is given, only that user's own
// actions are returned (each employee sees their own activity); admins/owners
// call without it to see the whole clinic.
export async function listActivity(
orgId: string,
limit = 100,
options: { actorId?: string; limit?: number } = {},
): Promise<ActivityEntry[]> {
const { actorId, limit = 100 } = options;
const rows = await db
.select()
.from(activityLog)
.where(eq(activityLog.organizationId, orgId))
.where(
actorId
? and(
eq(activityLog.organizationId, orgId),
eq(activityLog.actorId, actorId),
)
: eq(activityLog.organizationId, orgId),
)
.orderBy(desc(activityLog.createdAt))
.limit(limit);
return rows.map(toEntry);