# Phase 2 — Medicine Cabinet **Goal**: Track medicine inventory — what you have, how much of each, and when it expires. **Depends on**: Phase 0, Phase 1 (medicines) --- ## Deliverables 1. `CabinetItem` MongoDB schema and full CRUD API 2. Aggregate quantity view per medicine 3. Expiry date tracking with visual indicators 4. Medicine cabinet web UI with status indicators --- ## Data Model ### CabinetItem Schema ```typescript // packages/shared/src/types/cabinet.ts export interface CabinetItem { id: string; householdId: string; medicineId: string; // Reference to Medicine (generic level) medicineName: string; // Denormalized medicineStrength: number; // Denormalized for display medicineStrengthUnit: StrengthUnit; // Denormalized medicineForm: MedicineForm; // Denormalized medicineProductId?: string; // Optional reference to MedicineProduct (what was purchased) medicineProductBrand?: string; // Denormalized concentration?: number; // Denormalized from product (injection vials) concentrationUnit?: ConcentrationUnit; // Denormalized from product quantity: number; // Current quantity (independent of package size) unit: DosageUnit; expirationDate?: Date; status: CabinetItemStatus; notes?: string; createdBy: string; createdAt: Date; updatedAt: Date; } export enum CabinetItemStatus { ACTIVE = 'active', DEPLETED = 'depleted', EXPIRED = 'expired', } ``` ### CabinetSummary (Computed, not stored) ```typescript // Aggregate view — total per medicine across all cabinet items export interface CabinetSummary { medicineId: string; medicineName: string; medicineStrength: number; medicineStrengthUnit: StrengthUnit; medicineForm: MedicineForm; totalQuantity: number; unit: DosageUnit; earliestExpiry: Date | null; itemCount: number; // How many cabinet items (bottles/boxes) } ``` ### MongoDB Indexes ```javascript { householdId: 1, medicineId: 1, status: 1 } { householdId: 1, status: 1 } { householdId: 1, expirationDate: 1 } ``` --- ## API Endpoints ### CabinetModule | Method | Path | Description | Auth | | ------ | --------------------------- | ----------------------------------------------- | ------ | | GET | `/cabinet` | List cabinet items (filtered, paginated) | member | | GET | `/cabinet/summary` | Aggregate quantities per medicine | member | | GET | `/cabinet/:id` | Get single cabinet item | member | | POST | `/cabinet` | Add item to cabinet | member | | PATCH | `/cabinet/:id` | Update item (quantity, notes, etc.) | member | | POST | `/cabinet/:id/adjust` | Adjust quantity (add/subtract without full edit) | member | | DELETE | `/cabinet/:id` | Delete cabinet item | member | | GET | `/cabinet/expiring-soon` | Items expiring within N days | member | ### Query Parameters for GET `/cabinet` ``` ?medicineId=abc123 # Filter by medicine &status=active # Filter by status &expiringWithin=30 # Days until expiry &cursor=abc123 &limit=20 ``` ### Adjust Quantity Request ```typescript // POST /cabinet/:id/adjust interface AdjustQuantityRequest { delta: number; // Positive to add, negative to subtract reason?: string; // e.g., "Correcting count" } ``` --- ## Tasks ### 2.1 — Shared Types & Validation - Add cabinet types to `packages/shared/src/types/cabinet.ts` - Zod schemas: - `CreateCabinetItemSchema` - `UpdateCabinetItemSchema` - `AdjustQuantitySchema` - `CabinetQuerySchema` ### 2.2 — Mongoose Schema & Repository - `packages/api/src/modules/cabinet/cabinet.repository.ts` - `CabinetRepository` with: - `findByHousehold(householdId, query)` — filtered, paginated - `findById(id, householdId)` - `getAggregateSummary(householdId)` — MongoDB aggregation pipeline - `create(data)` - `update(id, householdId, data)` - `adjustQuantity(id, householdId, delta)` — atomic adjust with floor at 0 - `findExpiringSoon(householdId, withinDays)` - `softDelete(id, householdId)` ### 2.3 — Cabinet Service ```typescript class CabinetService { /** Add item, denormalizing medicine and product fields */ addItem(data: CreateCabinetItem): Promise; /** Adjust quantity with floor at 0, auto-set depleted status */ adjustQuantity(id: string, householdId: string, delta: number): Promise; /** Get aggregate summary */ getSummary(householdId: string): Promise; /** Find items expiring within N days */ getExpiringSoon(householdId: string, withinDays: number): Promise; } ``` ### 2.4 — Web UI: Medicine Cabinet - `/medicines/cabinet` page: - **Summary view** (default): aggregated per medicine - Medicine name, total quantity, earliest expiry - Expand to see individual items (bottles/boxes) - **Detail view**: all individual cabinet items with status filter - Each item shows: quantity, unit, expiry date, status badge - Color-coded expiry: green (>30 days), yellow (7-30 days), red (<7 days), bold red (expired) - Quick actions: adjust quantity (+/-), delete - "Add to Cabinet" form with medicine search/select - `/medicines/[id]` detail page: - Inventory section showing cabinet items for that medicine - Adjust and delete actions inline --- ## Acceptance Criteria - [x] Can add items to cabinet linked to medicines - [x] Aggregate summary shows total quantity per medicine - [x] Quantity adjustments are atomic and floor at 0 - [x] Items auto-transition to `depleted` when quantity reaches 0 - [x] Expiring-soon endpoint returns items within N days - [x] Web UI shows color-coded expiry indicators - [x] All cabinet queries are scoped to `householdId` - [x] Medicine detail page shows inventory for that medicine - [x] Concentration is denormalized from product for injection vials