mirror of
https://github.com/temetro/temetro.git
synced 2026-08-13 03:56:53 +00:00
backend: prescription start/end dates + dispense ledger
Add optional startDate/endDate columns to prescriptions (schema, validation, types, service) — when set, endDate drives expiry. Add a new append-only `dispenses` resource (schema, types, validation, service, route at /api/dispenses) recording who received which medication, gated on the existing inventory RBAC statement. Migration 0020. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { index, integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||
|
||||
import { organization, user } from "./auth.js";
|
||||
|
||||
// One row per dispensing event — the record of a medication actually handed to a
|
||||
// patient at the pharmacy, scoped to a clinic (organization). Distinct from
|
||||
// `prescriptions` (the order) and `inventory` (standing stock): this is the
|
||||
// fulfilment ledger ("who took which medication"). Patient + dispenser identity
|
||||
// are denormalized so the list renders without joining; `prescriptionId` links
|
||||
// back to the source order when the dispense came from the queue.
|
||||
export const dispenses = pgTable(
|
||||
"dispenses",
|
||||
{
|
||||
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().default(""),
|
||||
medication: text("medication").notNull(),
|
||||
dose: text("dose").notNull().default(""),
|
||||
quantity: integer("quantity").notNull().default(0),
|
||||
unit: text("unit").notNull().default(""),
|
||||
prescriptionId: uuid("prescription_id"),
|
||||
dispensedBy: text("dispensed_by").references(() => user.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
dispensedByName: text("dispensed_by_name").notNull().default(""),
|
||||
dispensedAt: timestamp("dispensed_at").defaultNow().notNull(),
|
||||
notes: text("notes"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
},
|
||||
(t) => [index("dispenses_org_idx").on(t.organizationId)],
|
||||
);
|
||||
@@ -5,6 +5,7 @@ export * from "./appointments.js";
|
||||
export * from "./prescriptions.js";
|
||||
export * from "./invoices.js";
|
||||
export * from "./inventory.js";
|
||||
export * from "./dispenses.js";
|
||||
export * from "./tasks.js";
|
||||
export * from "./activity.js";
|
||||
export * from "./messaging.js";
|
||||
|
||||
@@ -28,6 +28,11 @@ export const prescriptions = pgTable(
|
||||
frequency: text("frequency").notNull(),
|
||||
prescriber: text("prescriber").notNull(),
|
||||
prescribedAt: date("prescribed_at").defaultNow().notNull(),
|
||||
// Optional explicit course window. When set, `endDate` drives expiry instead
|
||||
// of parsing the free-text `duration`. Both are opt-in (the dialog reveals
|
||||
// them behind a toggle), so they stay nullable.
|
||||
startDate: date("start_date"),
|
||||
endDate: date("end_date"),
|
||||
status: text("status").$type<PrescriptionStatus>().notNull(),
|
||||
duration: text("duration"),
|
||||
notes: text("notes"),
|
||||
|
||||
Reference in New Issue
Block a user