πŸ“š Pharmacy Management Systems 18 min read

How to Build a Comprehensive Pharmacy Management System with Real-Time Inventory Tracking

Complete technical guide to developing a production-ready pharmacy management system with automated inventory control, expiration tracking, and intelligent reordering. Includes database design, barcode integration, and regulatory compliance.

✍️
Dr. Sarah Chen

Building a comprehensive pharmacy management system requires integrating medication ordering, inventory control, dispensing workflows, and regulatory compliance into a unified platform. This guide walks through creating a production-ready system that manages thousands of medications, tracks expiration dates, automates reordering, and ensures compliance with DEA and state pharmacy regulations.

JustCopy.ai’s 10 specialized AI agents can generate this entire pharmacy management system automatically, creating database schemas, inventory algorithms, regulatory compliance logic, and integration interfaces in days instead of months.

System Architecture Overview

A modern pharmacy management system consists of several integrated components:

  1. Medication Master Database: Comprehensive drug information database
  2. Inventory Management: Real-time tracking across multiple locations
  3. Order Management: Processing prescriptions and medication orders
  4. Dispensing Workflow: Barcode verification and safety checks
  5. Controlled Substance Tracking: DEA-compliant chain of custody
  6. Analytics Engine: Usage patterns and cost optimization
  7. Integration Layer: Connections to EHR, ADCs, and wholesalers

Here’s the complete architecture:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              External Systems                        β”‚
β”‚  EHR/EMR β”‚ Wholesalers β”‚ ADCs β”‚ Insurance           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Integration & API Layer                    β”‚
β”‚  - HL7/FHIR interfaces                              β”‚
β”‚  - EDI for wholesaler orders                        β”‚
β”‚  - Real-time ADC sync                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β–Ό                       β–Ό             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Inventory  β”‚      β”‚    Order     β”‚  β”‚Dispensing  β”‚
β”‚  Management  │◄────►│ Management   │◄──  Workflow  β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                     β”‚
       β–Ό                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Medication  β”‚      β”‚ Controlled   β”‚
β”‚   Database   β”‚      β”‚  Substance   β”‚
β”‚              β”‚      β”‚   Tracking   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                     β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β–Ό
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚   Analytics    β”‚
         β”‚  & Reporting   β”‚
         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Database Schema Design

The pharmacy database must track medications, inventory locations, transactions, and controlled substances with complete audit trails:

-- Comprehensive medication master database
CREATE TABLE medications (
    medication_id SERIAL PRIMARY KEY,
    ndc_code VARCHAR(11) UNIQUE NOT NULL, -- National Drug Code
    name VARCHAR(200) NOT NULL,
    generic_name VARCHAR(200),
    brand_name VARCHAR(200),
    manufacturer VARCHAR(200),

    -- Pharmaceutical details
    drug_class VARCHAR(100),
    therapeutic_category VARCHAR(100),
    dosage_form VARCHAR(50), -- tablet, capsule, injection, etc.
    strength VARCHAR(50),
    strength_unit VARCHAR(20),
    route VARCHAR(50),

    -- Regulatory classification
    dea_schedule VARCHAR(5), -- C-II, C-III, C-IV, C-V, or NULL
    is_controlled BOOLEAN DEFAULT FALSE,
    is_high_alert BOOLEAN DEFAULT FALSE,
    requires_refrigeration BOOLEAN DEFAULT FALSE,

    -- Pricing
    unit_cost DECIMAL(10,2),
    awp DECIMAL(10,2), -- Average Wholesale Price
    340b_price DECIMAL(10,2), -- 340B drug pricing program

    -- Inventory management
    reorder_point INTEGER DEFAULT 50,
    reorder_quantity INTEGER DEFAULT 100,
    max_stock_level INTEGER DEFAULT 500,

    -- Status
    active BOOLEAN DEFAULT TRUE,
    formulary_status VARCHAR(20) DEFAULT 'preferred',

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_medications_ndc ON medications(ndc_code);
CREATE INDEX idx_medications_name ON medications(name);
CREATE INDEX idx_medications_generic ON medications(generic_name);
CREATE INDEX idx_medications_controlled ON medications(is_controlled);
CREATE INDEX idx_medications_dea_schedule ON medications(dea_schedule);

-- Multi-location inventory tracking
CREATE TABLE inventory_locations (
    location_id SERIAL PRIMARY KEY,
    location_name VARCHAR(100) NOT NULL,
    location_type VARCHAR(50), -- 'main_pharmacy', 'satellite', 'cabinet', 'refrigerator'
    parent_location_id INTEGER REFERENCES inventory_locations(location_id),
    facility_id INTEGER,
    requires_access_control BOOLEAN DEFAULT FALSE,
    temperature_controlled BOOLEAN DEFAULT FALSE,
    is_active BOOLEAN DEFAULT TRUE
);

CREATE INDEX idx_locations_type ON inventory_locations(location_type);
CREATE INDEX idx_locations_facility ON inventory_locations(facility_id);

-- Real-time inventory tracking
CREATE TABLE inventory (
    inventory_id BIGSERIAL PRIMARY KEY,
    medication_id INTEGER REFERENCES medications(medication_id),
    location_id INTEGER REFERENCES inventory_locations(location_id),

    -- Quantity tracking
    quantity_on_hand INTEGER NOT NULL DEFAULT 0,
    quantity_allocated INTEGER DEFAULT 0, -- Reserved for pending orders
    quantity_available INTEGER GENERATED ALWAYS AS (quantity_on_hand - quantity_allocated) STORED,

    -- Lot tracking
    lot_number VARCHAR(50),
    expiration_date DATE NOT NULL,

    -- Receiving information
    received_date DATE,
    received_from VARCHAR(200), -- Wholesaler name
    purchase_order_number VARCHAR(50),
    unit_cost DECIMAL(10,2),

    -- Status
    quarantine_status BOOLEAN DEFAULT FALSE,
    quarantine_reason TEXT,

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT positive_quantity CHECK (quantity_on_hand >= 0),
    CONSTRAINT valid_expiration CHECK (expiration_date > received_date)
);

CREATE INDEX idx_inventory_medication ON inventory(medication_id);
CREATE INDEX idx_inventory_location ON inventory(location_id);
CREATE INDEX idx_inventory_expiration ON inventory(expiration_date);
CREATE INDEX idx_inventory_lot ON inventory(lot_number);

-- Medication orders/prescriptions
CREATE TABLE medication_orders (
    order_id BIGSERIAL PRIMARY KEY,
    patient_mrn VARCHAR(50) NOT NULL,
    medication_id INTEGER REFERENCES medications(medication_id),

    -- Prescriber information
    prescriber_id INTEGER NOT NULL,
    prescriber_name VARCHAR(200),
    prescriber_npi VARCHAR(10),
    prescriber_dea VARCHAR(20),

    -- Order details
    dose DECIMAL(10,2),
    dose_unit VARCHAR(20),
    route VARCHAR(50),
    frequency VARCHAR(100),
    duration_days INTEGER,
    quantity_to_dispense INTEGER,
    refills_authorized INTEGER DEFAULT 0,
    refills_remaining INTEGER DEFAULT 0,

    -- Timing
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    start_date TIMESTAMP,
    end_date TIMESTAMP,

    -- Clinical information
    diagnosis_code VARCHAR(20),
    indication TEXT,
    special_instructions TEXT,

    -- Status
    order_status VARCHAR(20) DEFAULT 'pending', -- pending, verified, dispensed, completed, cancelled
    priority VARCHAR(20) DEFAULT 'routine', -- stat, urgent, routine

    -- Regulatory
    is_controlled BOOLEAN DEFAULT FALSE,
    dea_form_number VARCHAR(50), -- For controlled substances

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_orders_patient ON medication_orders(patient_mrn);
CREATE INDEX idx_orders_medication ON medication_orders(medication_id);
CREATE INDEX idx_orders_status ON medication_orders(order_status);
CREATE INDEX idx_orders_prescriber ON medication_orders(prescriber_id);
CREATE INDEX idx_orders_controlled ON medication_orders(is_controlled);

-- Dispensing transactions
CREATE TABLE dispensing_transactions (
    transaction_id BIGSERIAL PRIMARY KEY,
    order_id BIGINT REFERENCES medication_orders(order_id),
    inventory_id BIGINT REFERENCES inventory(inventory_id),

    -- Transaction details
    quantity_dispensed INTEGER NOT NULL,
    dispensed_by INTEGER NOT NULL, -- User ID of pharmacist
    verified_by INTEGER, -- User ID of verifying pharmacist
    dispensed_to INTEGER, -- User ID of nurse/patient

    -- Timing
    dispensed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    -- Location
    dispensing_location_id INTEGER REFERENCES inventory_locations(location_id),

    -- Patient information (denormalized for audit)
    patient_mrn VARCHAR(50),
    patient_name VARCHAR(200),

    -- Barcode verification
    medication_barcode_scanned VARCHAR(100),
    patient_barcode_scanned VARCHAR(100),

    -- Controlled substance specific
    witness_id INTEGER, -- Required for controlled substances
    recipient_signature BYTEA, -- Digital signature

    -- Billing
    billed_amount DECIMAL(10,2),
    insurance_paid DECIMAL(10,2),
    patient_paid DECIMAL(10,2),

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_dispensing_order ON dispensing_transactions(order_id);
CREATE INDEX idx_dispensing_inventory ON dispensing_transactions(inventory_id);
CREATE INDEX idx_dispensing_date ON dispensing_transactions(dispensed_at DESC);
CREATE INDEX idx_dispensing_patient ON dispensing_transactions(patient_mrn);

-- Controlled substance tracking (DEA compliance)
CREATE TABLE controlled_substance_log (
    log_id BIGSERIAL PRIMARY KEY,
    medication_id INTEGER REFERENCES medications(medication_id),
    inventory_id BIGINT REFERENCES inventory(inventory_id),

    -- Transaction type
    transaction_type VARCHAR(20) NOT NULL, -- receive, dispense, return, waste, transfer

    -- Quantity tracking
    quantity INTEGER NOT NULL,
    balance_after_transaction INTEGER NOT NULL,

    -- Personnel
    performed_by INTEGER NOT NULL,
    witnessed_by INTEGER NOT NULL, -- All controlled substance transactions require witness

    -- Related records
    dispensing_transaction_id BIGINT REFERENCES dispensing_transactions(transaction_id),
    purchase_order_number VARCHAR(50),

    -- DEA information
    dea_form_number VARCHAR(50),
    dea_form_type VARCHAR(10), -- 222, 41, etc.

    -- Reason/notes
    transaction_reason TEXT,

    -- Timestamp
    transaction_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT valid_transaction_type CHECK (
        transaction_type IN ('receive', 'dispense', 'return', 'waste', 'transfer', 'adjustment')
    )
);

CREATE INDEX idx_cs_log_medication ON controlled_substance_log(medication_id);
CREATE INDEX idx_cs_log_timestamp ON controlled_substance_log(transaction_timestamp DESC);
CREATE INDEX idx_cs_log_type ON controlled_substance_log(transaction_type);

-- Automatic reorder management
CREATE TABLE reorder_requests (
    request_id SERIAL PRIMARY KEY,
    medication_id INTEGER REFERENCES medications(medication_id),
    location_id INTEGER REFERENCES inventory_locations(location_id),

    -- Request details
    requested_quantity INTEGER NOT NULL,
    current_quantity INTEGER,
    reason VARCHAR(100), -- 'below_reorder_point', 'expiring_soon', 'manual'

    -- Status
    request_status VARCHAR(20) DEFAULT 'pending', -- pending, approved, ordered, received, cancelled

    -- Approval workflow
    requested_by INTEGER,
    requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    approved_by INTEGER,
    approved_at TIMESTAMP,

    -- Purchase order
    purchase_order_number VARCHAR(50),
    ordered_at TIMESTAMP,
    expected_delivery_date DATE,

    -- Receiving
    received_quantity INTEGER,
    received_at TIMESTAMP,

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_reorder_status ON reorder_requests(request_status);
CREATE INDEX idx_reorder_medication ON reorder_requests(medication_id);

JustCopy.ai automatically generates this comprehensive database schema with proper indexing, constraints, and audit trail design optimized for pharmacy operations.

Core Inventory Management Implementation

The inventory management engine tracks stock levels in real-time and triggers automated reordering:

# Pharmacy Inventory Management System
# Real-time tracking with automated reordering
# Built with JustCopy.ai's backend agent

from datetime import datetime, timedelta
from decimal import Decimal

class PharmacyInventoryManager:
    def __init__(self, db_connection):
        self.db = db_connection

    async def receive_medication(self, medication_id, location_id, quantity,
                                 lot_number, expiration_date, unit_cost,
                                 po_number, wholesaler):
        """
        Receive medication shipment into inventory
        """
        try:
            # Validate expiration date
            if expiration_date <= datetime.now().date():
                return {
                    'success': False,
                    'error': 'Medication already expired'
                }

            # Check if medication requires refrigeration
            medication = await self._get_medication(medication_id)
            location = await self._get_location(location_id)

            if medication['requires_refrigeration'] and not location['temperature_controlled']:
                return {
                    'success': False,
                    'error': 'Medication requires temperature-controlled storage'
                }

            # Create inventory record
            query = """
                INSERT INTO inventory (
                    medication_id, location_id, quantity_on_hand,
                    lot_number, expiration_date, received_date,
                    received_from, purchase_order_number, unit_cost
                )
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
                RETURNING inventory_id
            """

            result = self.db.execute(query, (
                medication_id, location_id, quantity,
                lot_number, expiration_date, datetime.now().date(),
                wholesaler, po_number, unit_cost
            ))

            inventory_id = result.fetchone()[0]
            self.db.commit()

            # If controlled substance, log receipt
            if medication['is_controlled']:
                await self._log_controlled_substance_receipt(
                    medication_id, inventory_id, quantity, po_number
                )

            # Update reorder request if this fulfills one
            await self._update_reorder_request(medication_id, po_number, quantity)

            # Check if we now have excess inventory
            await self._check_excess_inventory(medication_id, location_id)

            print(f"Received {quantity} units of {medication['name']}")

            return {
                'success': True,
                'inventory_id': inventory_id,
                'quantity': quantity
            }

        except Exception as e:
            self.db.rollback()
            return {'success': False, 'error': str(e)}

    async def allocate_inventory(self, order_id):
        """
        Allocate inventory for medication order
        Uses FEFO (First Expiry, First Out) logic
        """
        try:
            # Get order details
            order = await self._get_order(order_id)

            # Find available inventory using FEFO
            inventory = await self._find_inventory_fefo(
                medication_id=order['medication_id'],
                quantity_needed=order['quantity_to_dispense']
            )

            if not inventory['sufficient']:
                return {
                    'success': False,
                    'error': 'Insufficient inventory',
                    'available': inventory['available_quantity'],
                    'needed': order['quantity_to_dispense']
                }

            # Allocate from inventory records
            allocations = []
            remaining_quantity = order['quantity_to_dispense']

            for inv_record in inventory['records']:
                allocate_qty = min(remaining_quantity, inv_record['quantity_available'])

                # Update allocation
                update_query = """
                    UPDATE inventory
                    SET quantity_allocated = quantity_allocated + %s,
                        last_updated = CURRENT_TIMESTAMP
                    WHERE inventory_id = %s
                """

                self.db.execute(update_query, (allocate_qty, inv_record['inventory_id']))

                allocations.append({
                    'inventory_id': inv_record['inventory_id'],
                    'lot_number': inv_record['lot_number'],
                    'expiration_date': inv_record['expiration_date'],
                    'quantity': allocate_qty
                })

                remaining_quantity -= allocate_qty

                if remaining_quantity == 0:
                    break

            self.db.commit()

            return {
                'success': True,
                'allocations': allocations
            }

        except Exception as e:
            self.db.rollback()
            return {'success': False, 'error': str(e)}

    async def _find_inventory_fefo(self, medication_id, quantity_needed):
        """
        Find inventory using First Expiry, First Out logic
        """
        query = """
            SELECT
                inventory_id,
                lot_number,
                expiration_date,
                quantity_available
            FROM inventory
            WHERE medication_id = %s
              AND quantity_available > 0
              AND quarantine_status = FALSE
              AND expiration_date > CURRENT_DATE
            ORDER BY expiration_date ASC, received_date ASC
        """

        result = self.db.execute(query, (medication_id,))
        records = [dict(row) for row in result.fetchall()]

        # Calculate if we have sufficient inventory
        total_available = sum(r['quantity_available'] for r in records)
        sufficient = total_available >= quantity_needed

        return {
            'sufficient': sufficient,
            'available_quantity': total_available,
            'records': records
        }

    async def dispense_medication(self, order_id, dispensed_by, verified_by=None):
        """
        Complete medication dispensing transaction
        """
        try:
            # Get order and allocations
            order = await self._get_order(order_id)
            allocations = await self._get_allocations(order_id)

            if not allocations:
                return {
                    'success': False,
                    'error': 'No inventory allocated for this order'
                }

            # Create dispensing transactions for each allocation
            transaction_ids = []

            for allocation in allocations:
                # Create dispensing transaction
                trans_query = """
                    INSERT INTO dispensing_transactions (
                        order_id, inventory_id, quantity_dispensed,
                        dispensed_by, verified_by, patient_mrn,
                        dispensing_location_id, dispensed_at
                    )
                    VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
                    RETURNING transaction_id
                """

                result = self.db.execute(trans_query, (
                    order_id, allocation['inventory_id'], allocation['quantity'],
                    dispensed_by, verified_by, order['patient_mrn'],
                    allocation['location_id'], datetime.utcnow()
                ))

                transaction_id = result.fetchone()[0]
                transaction_ids.append(transaction_id)

                # Update inventory - remove from on_hand and allocated
                inv_update = """
                    UPDATE inventory
                    SET quantity_on_hand = quantity_on_hand - %s,
                        quantity_allocated = quantity_allocated - %s,
                        last_updated = CURRENT_TIMESTAMP
                    WHERE inventory_id = %s
                """

                self.db.execute(inv_update, (
                    allocation['quantity'],
                    allocation['quantity'],
                    allocation['inventory_id']
                ))

                # If controlled substance, log dispensing
                if order['is_controlled']:
                    await self._log_controlled_substance_dispensing(
                        medication_id=order['medication_id'],
                        inventory_id=allocation['inventory_id'],
                        quantity=allocation['quantity'],
                        transaction_id=transaction_id,
                        dispensed_by=dispensed_by,
                        witnessed_by=verified_by
                    )

            # Update order status
            order_update = """
                UPDATE medication_orders
                SET order_status = 'dispensed',
                    refills_remaining = refills_remaining - 1,
                    updated_at = CURRENT_TIMESTAMP
                WHERE order_id = %s
            """

            self.db.execute(order_update, (order_id,))
            self.db.commit()

            # Check if reorder needed
            await self._check_reorder_point(order['medication_id'])

            return {
                'success': True,
                'transaction_ids': transaction_ids,
                'quantity_dispensed': order['quantity_to_dispense']
            }

        except Exception as e:
            self.db.rollback()
            return {'success': False, 'error': str(e)}

    async def _check_reorder_point(self, medication_id):
        """
        Check if medication below reorder point and trigger automatic reorder
        """
        medication = await self._get_medication(medication_id)

        # Calculate total available inventory
        query = """
            SELECT COALESCE(SUM(quantity_available), 0) as total_available
            FROM inventory
            WHERE medication_id = %s
              AND quarantine_status = FALSE
              AND expiration_date > CURRENT_DATE + INTERVAL '30 days'
        """

        result = self.db.execute(query, (medication_id,))
        total_available = result.fetchone()['total_available']

        if total_available <= medication['reorder_point']:
            # Check if reorder already pending
            pending_query = """
                SELECT COUNT(*) as pending_count
                FROM reorder_requests
                WHERE medication_id = %s
                  AND request_status IN ('pending', 'approved', 'ordered')
            """

            pending_result = self.db.execute(pending_query, (medication_id,))
            pending_count = pending_result.fetchone()['pending_count']

            if pending_count == 0:
                # Create reorder request
                await self._create_reorder_request(
                    medication_id=medication_id,
                    quantity=medication['reorder_quantity'],
                    reason='below_reorder_point',
                    current_quantity=total_available
                )

    async def _create_reorder_request(self, medication_id, quantity, reason, current_quantity):
        """
        Create automatic reorder request
        """
        query = """
            INSERT INTO reorder_requests (
                medication_id, requested_quantity, current_quantity,
                reason, request_status, requested_at
            )
            VALUES (%s, %s, %s, %s, 'pending', %s)
            RETURNING request_id
        """

        result = self.db.execute(query, (
            medication_id, quantity, current_quantity,
            reason, datetime.utcnow()
        ))

        request_id = result.fetchone()[0]
        self.db.commit()

        # Send notification to pharmacy manager
        await self._notify_reorder_needed(request_id, medication_id, quantity)

        return request_id

    async def check_expiring_medications(self, days_threshold=90):
        """
        Identify medications expiring soon
        """
        query = """
            SELECT
                m.name,
                m.generic_name,
                i.lot_number,
                i.expiration_date,
                i.quantity_on_hand,
                i.unit_cost,
                (i.quantity_on_hand * i.unit_cost) as total_value,
                l.location_name,
                CURRENT_DATE - i.expiration_date as days_until_expiration
            FROM inventory i
            JOIN medications m ON i.medication_id = m.medication_id
            JOIN inventory_locations l ON i.location_id = l.location_id
            WHERE i.expiration_date <= CURRENT_DATE + INTERVAL '%s days'
              AND i.quantity_on_hand > 0
              AND i.quarantine_status = FALSE
            ORDER BY i.expiration_date ASC
        """

        result = self.db.execute(query, (days_threshold,))
        expiring_meds = [dict(row) for row in result.fetchall()]

        # Calculate total value at risk
        total_value = sum(med['total_value'] for med in expiring_meds)

        return {
            'expiring_medications': expiring_meds,
            'total_count': len(expiring_meds),
            'total_value_at_risk': float(total_value)
        }

    async def perform_cycle_count(self, location_id, medication_id=None):
        """
        Perform inventory cycle count
        """
        # Get expected inventory
        query = """
            SELECT
                i.inventory_id,
                m.medication_id,
                m.name,
                i.lot_number,
                i.expiration_date,
                i.quantity_on_hand as expected_quantity
            FROM inventory i
            JOIN medications m ON i.medication_id = m.medication_id
            WHERE i.location_id = %s
              AND i.quantity_on_hand > 0
        """

        params = [location_id]

        if medication_id:
            query += " AND m.medication_id = %s"
            params.append(medication_id)

        result = self.db.execute(query, params)
        items_to_count = [dict(row) for row in result.fetchall()]

        return {
            'cycle_count_id': await self._create_cycle_count_session(location_id),
            'items_to_count': items_to_count,
            'total_items': len(items_to_count)
        }

# Controlled substance tracking for DEA compliance
class ControlledSubstanceTracker:
    async def log_transaction(self, medication_id, inventory_id, transaction_type,
                              quantity, performed_by, witnessed_by, reason=None):
        """
        Log controlled substance transaction with witness
        """
        # Get current balance
        current_balance = await self._get_current_balance(inventory_id)

        # Calculate new balance
        if transaction_type in ['receive', 'return']:
            new_balance = current_balance + quantity
        elif transaction_type in ['dispense', 'waste', 'transfer']:
            new_balance = current_balance - quantity
        else:
            new_balance = current_balance

        # Log transaction
        query = """
            INSERT INTO controlled_substance_log (
                medication_id, inventory_id, transaction_type,
                quantity, balance_after_transaction,
                performed_by, witnessed_by, transaction_reason,
                transaction_timestamp
            )
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
            RETURNING log_id
        """

        result = self.db.execute(query, (
            medication_id, inventory_id, transaction_type,
            quantity, new_balance, performed_by, witnessed_by,
            reason, datetime.utcnow()
        ))

        log_id = result.fetchone()[0]
        self.db.commit()

        return log_id

    async def reconcile_controlled_substances(self, medication_id):
        """
        Reconcile controlled substance inventory with transaction log
        """
        # Get physical inventory count
        physical_query = """
            SELECT COALESCE(SUM(quantity_on_hand), 0) as physical_count
            FROM inventory
            WHERE medication_id = %s
        """

        physical_result = self.db.execute(physical_query, (medication_id,))
        physical_count = physical_result.fetchone()['physical_count']

        # Get ledger balance from transaction log
        ledger_query = """
            SELECT balance_after_transaction
            FROM controlled_substance_log
            WHERE medication_id = %s
            ORDER BY transaction_timestamp DESC
            LIMIT 1
        """

        ledger_result = self.db.execute(ledger_query, (medication_id,))
        ledger_row = ledger_result.fetchone()

        ledger_balance = ledger_row['balance_after_transaction'] if ledger_row else 0

        # Check for discrepancy
        discrepancy = physical_count - ledger_balance

        return {
            'medication_id': medication_id,
            'physical_count': physical_count,
            'ledger_balance': ledger_balance,
            'discrepancy': discrepancy,
            'reconciled': discrepancy == 0
        }

JustCopy.ai generates this complete inventory management system with FEFO logic, automatic reordering, and DEA-compliant controlled substance tracking.

Implementation Timeline

16-Week Implementation:

  • Weeks 1-3: Database design and medication master setup
  • Weeks 4-7: Core inventory management implementation
  • Weeks 8-10: Order management and dispensing workflows
  • Weeks 11-12: Controlled substance tracking and compliance
  • Weeks 13-14: Integration with EHR, ADCs, and wholesalers
  • Weeks 15-16: Testing, validation, and go-live

Using JustCopy.ai, this timeline reduces to 6-8 weeks as the platform automatically generates 75% of the codebase.

ROI Calculation

500-Bed Hospital:

Costs:

  • Development (with JustCopy.ai): $145,000
  • Hardware (barcode scanners, labels): $35,000
  • Annual software maintenance: $45,000

Benefits:

  • Reduced medication waste from expiration: $285,000/year
  • Optimized inventory carrying costs: $195,000/year
  • Eliminated manual counting (pharmacy tech time): $145,000/year
  • Prevented stockouts: $95,000/year
  • Improved controlled substance compliance: Priceless
  • Total annual benefit: $720,000

First-Year ROI: 300% 3-Year ROI: 892%

JustCopy.ai makes comprehensive pharmacy management system development accessible to healthcare organizations, automatically generating inventory algorithms, regulatory compliance logic, and integration codeβ€”all customizable to specific pharmacy workflows and requirements.

πŸš€

Build This with JustCopy.ai

Skip months of development with 10 specialized AI agents. JustCopy.ai can copy, customize, and deploy this application instantly. Our AI agents write code, run tests, handle deployment, and monitor your applicationβ€”all following healthcare industry best practices and HIPAA compliance standards.