feat: org-scoped prescriptions backend, wire prescriptions page

Add the prescriptions table, validation, service and CRUD routes
(/api/prescriptions, RBAC-gated; prescriber defaults to the signed-in
clinician, prescribedAt to today) and the frontend data module. The page
now loads/persists real data and computes its status KPIs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-06-07 19:37:46 +03:00
parent dec04ec506
commit 7ef9da59bd
14 changed files with 2195 additions and 111 deletions
+1
View File
@@ -2,3 +2,4 @@ export * from "./auth.js";
export * from "./patients.js";
export * from "./notes.js";
export * from "./appointments.js";
export * from "./prescriptions.js";
+44
View File
@@ -0,0 +1,44 @@
import {
date,
index,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import type { PrescriptionStatus } from "../../types/prescription.js";
import { organization, user } from "./auth.js";
// One row per prescribed medication, scoped to a clinic (organization). Patient
// identity is denormalized (name/initials/file number) so the ledger renders
// without joining. `prescribedAt` defaults to today.
export const prescriptions = pgTable(
"prescriptions",
{
id: uuid("id").primaryKey().defaultRandom(),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
patientFileNumber: text("patient_file_number").notNull().default(""),
patientName: text("patient_name").notNull(),
patientInitials: text("patient_initials").notNull(),
medication: text("medication").notNull(),
dose: text("dose").notNull().default(""),
frequency: text("frequency").notNull(),
prescriber: text("prescriber").notNull(),
prescribedAt: date("prescribed_at").defaultNow().notNull(),
status: text("status").$type<PrescriptionStatus>().notNull(),
duration: text("duration"),
notes: text("notes"),
createdBy: text("created_by").references(() => user.id, {
onDelete: "set null",
}),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => new Date())
.notNull(),
},
(t) => [index("prescriptions_org_idx").on(t.organizationId)],
);